Merge remote-tracking branch 'origin/master' into BETA_JAVA18
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
index f998810..5125b72 100644
--- a/.mvn/extensions.xml
+++ b/.mvn/extensions.xml
@@ -3,6 +3,6 @@
   <extension>
     <groupId>org.eclipse.tycho.extras</groupId>
     <artifactId>tycho-pomless</artifactId>
-    <version>2.5.0</version>
+    <version>2.6.0</version>
   </extension>
 </extensions>
\ No newline at end of file
diff --git a/apitools/org.eclipse.pde.api.tools.annotations/pom.xml b/apitools/org.eclipse.pde.api.tools.annotations/pom.xml
index aea83b2..89664ad 100644
--- a/apitools/org.eclipse.pde.api.tools.annotations/pom.xml
+++ b/apitools/org.eclipse.pde.api.tools.annotations/pom.xml
@@ -10,10 +10,9 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui.apitools</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.api.tools.annotations</artifactId>
   <version>1.1.400-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/apitools/org.eclipse.pde.api.tools.tests/pom.xml b/apitools/org.eclipse.pde.api.tools.tests/pom.xml
index 7237f1b..7d04219 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/pom.xml
+++ b/apitools/org.eclipse.pde.api.tools.tests/pom.xml
@@ -13,11 +13,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>tests-pom</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../tests-pom/</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.api.tools.tests</artifactId>
   <version>1.2.500-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/ApiBuilderTest.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/ApiBuilderTest.java
index 9b33877..95273f5 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/ApiBuilderTest.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/builder/tests/ApiBuilderTest.java
@@ -66,7 +66,9 @@
 import org.eclipse.pde.api.tools.model.tests.TestSuiteHelper;
 import org.eclipse.pde.api.tools.tests.util.FileUtils;
 import org.eclipse.pde.api.tools.tests.util.ProjectUtils;
+import org.eclipse.pde.internal.core.ICoreConstants;
 import org.eclipse.pde.internal.core.PDECore;
+import org.eclipse.pde.ui.tests.util.FreezeMonitor;
 import org.eclipse.ui.dialogs.IOverwriteQuery;
 import org.eclipse.ui.wizards.datatransfer.FileSystemStructureProvider;
 import org.eclipse.ui.wizards.datatransfer.ImportOperation;
@@ -94,6 +96,11 @@
 	public static final String BIN_ROOT = "bin"; //$NON-NLS-1$
 	protected final int[] NO_PROBLEM_IDS = new int[0];
 
+	@BeforeClass
+	public static void beforeClass() {
+		PDECore.getDefault().getPreferencesManager().setValue(ICoreConstants.RUN_API_ANALYSIS_AS_JOB, false);
+	}
+
 	/**
 	 * Describes a line number mapped to the problem id with the given args we
 	 * expect to see there
@@ -1061,6 +1068,7 @@
 	 */
 	@Override
 	protected void setUp() throws Exception {
+		FreezeMonitor.expectCompletionInAMinute();
 		if (env == null) {
 			env = new ApiTestingEnvironment();
 			env.openEmptyWorkspace();
@@ -1077,6 +1085,7 @@
 		fMessageArgs = null;
 		this.debugRequestor.clearResult();
 		super.tearDown();
+		FreezeMonitor.done();
 	}
 
 	/**
@@ -1198,7 +1207,7 @@
 			IStatus infos = new Status(severity, PDECore.PLUGIN_ID, infosContent, new Exception());
 			log.log(infos);
 		} catch (Exception e) {
-			IStatus error = new Status(IStatus.ERROR, PDECore.PLUGIN_ID, "error occurred while logging extra info", e); //$NON-NLS-1$
+			IStatus error = Status.error("error occurred while logging extra info", e); //$NON-NLS-1$
 			log.log(error);
 		}
 	}
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/comparator/tests/DeltaTestSetup.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/comparator/tests/DeltaTestSetup.java
index 251253f..53a5e09 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/comparator/tests/DeltaTestSetup.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/comparator/tests/DeltaTestSetup.java
@@ -31,6 +31,7 @@
 import org.eclipse.pde.api.tools.internal.provisional.comparator.IDelta;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
 import org.eclipse.pde.api.tools.model.tests.TestSuiteHelper;
+import org.eclipse.pde.ui.tests.util.FreezeMonitor;
 import org.junit.After;
 import org.junit.Before;
 
@@ -205,6 +206,7 @@
 
 	@Before
 	public void setUp() throws Exception {
+		FreezeMonitor.expectCompletionInAMinute();
 		// create workspace root
 		new File(WORKSPACE_ROOT.toOSString()).mkdirs();
 	}
@@ -220,5 +222,6 @@
 		}
 		// remove workspace root
 		assertTrue(TestSuiteHelper.delete(new File(WORKSPACE_ROOT.toOSString())));
+		FreezeMonitor.done();
 	}
 }
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/ApiFilterStoreTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/ApiFilterStoreTests.java
index 770c7ed..67ff08a 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/ApiFilterStoreTests.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/ApiFilterStoreTests.java
@@ -61,8 +61,10 @@
 	private static final IPath XML_LOC = TestSuiteHelper.getPluginDirectoryPath().append("test-xml"); //$NON-NLS-1$
 	private static final IPath PLUGIN_LOC = TestSuiteHelper.getPluginDirectoryPath().append("test-plugins"); //$NON-NLS-1$
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
+		super.setUp();
 		createProject(TESTING_PLUGIN_PROJECT_NAME, null);
 		File projectSrc = SRC_LOC.toFile();
 		assertTrue("the filter source dir must exist", projectSrc.exists()); //$NON-NLS-1$
@@ -84,9 +86,11 @@
 		assertNotNull("the .api_filters file must exist in the testing project", filters); //$NON-NLS-1$
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		deleteProject(TESTING_PLUGIN_PROJECT_NAME);
+		super.tearDown();
 	}
 
 	/**
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/BadClassfileTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/BadClassfileTests.java
index 943dfa8..ebb33cd 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/BadClassfileTests.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/BadClassfileTests.java
@@ -22,6 +22,7 @@
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.osgi.service.resolver.ResolverError;
@@ -222,7 +223,7 @@
 			}
 
 			@Override
-			public boolean acceptReference(IReference reference) {
+			public boolean acceptReference(IReference reference, IProgressMonitor monitor) {
 				return true;
 			}
 
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/FilterStoreTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/FilterStoreTests.java
index 60e4ff1..adcdee2 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/FilterStoreTests.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/FilterStoreTests.java
@@ -57,8 +57,10 @@
 
 	private BundleComponent fComponent = null;
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
+		super.setUp();
 		createProject(TESTING_PLUGIN_PROJECT_NAME, null);
 		File projectSrc = SRC_LOC.toFile();
 		assertTrue("the filter source dir must exist", projectSrc.exists()); //$NON-NLS-1$
@@ -80,9 +82,11 @@
 		assertNotNull("the .api_filters file must exist in the testing project", filters); //$NON-NLS-1$
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		deleteProject(TESTING_PLUGIN_PROJECT_NAME);
+		super.tearDown();
 	}
 
 	private BundleComponent getComponent() throws CoreException {
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/TestSuiteHelper.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/TestSuiteHelper.java
index 0011bfc..32f6b0f 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/TestSuiteHelper.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/model/tests/TestSuiteHelper.java
@@ -29,7 +29,6 @@
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.FileLocator;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
@@ -51,7 +50,6 @@
 import org.eclipse.pde.api.tools.internal.search.IReferenceCollection;
 import org.eclipse.pde.api.tools.internal.search.UseScanReferences;
 import org.eclipse.pde.api.tools.internal.util.Util;
-import org.eclipse.pde.api.tools.tests.ApiTestsPlugin;
 import org.eclipse.pde.api.tools.tests.util.ProjectUtils;
 import org.junit.Assert;
 import org.osgi.framework.Bundle;
@@ -300,6 +298,11 @@
 				}
 				return fReferences;
 			}
+
+			@Override
+			public boolean isDisposed() {
+				return false;
+			}
 		};
 	}
 
@@ -491,7 +494,7 @@
 			}
 		}
 		if (error) {
-			throw new CoreException(new Status(IStatus.ERROR, ApiTestsPlugin.PLUGIN_ID, "Check the property : -DrequiredBundles=...\nMissing required bundle(s): " + String.valueOf(buffer))); //$NON-NLS-1$
+			throw new CoreException(Status.error("Check the property : -DrequiredBundles=...\nMissing required bundle(s): " + String.valueOf(buffer))); //$NON-NLS-1$
 		}
 	}
 
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/search/tests/SearchTest.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/search/tests/SearchTest.java
index 51fdbc3..1cf0a8a 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/search/tests/SearchTest.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/search/tests/SearchTest.java
@@ -30,6 +30,7 @@
 import org.eclipse.pde.api.tools.internal.util.FilteredElements;
 import org.eclipse.pde.api.tools.internal.util.Util;
 import org.eclipse.pde.api.tools.model.tests.TestSuiteHelper;
+import org.eclipse.pde.ui.tests.util.FreezeMonitor;
 import org.junit.After;
 import org.junit.Before;
 
@@ -169,6 +170,7 @@
 	public void setUp() throws Exception {
 		TEST_REQUESTOR = new TestRequestor(this);
 		TEST_REPORTER = new TestReporter(this);
+		FreezeMonitor.expectCompletionInAMinute();
 	}
 
 	@After
@@ -179,5 +181,6 @@
 		if (this.scope != null) {
 			this.scope.dispose();
 		}
+		FreezeMonitor.done();
 	}
 }
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..1742a91 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
@@ -16,6 +16,7 @@
 import java.util.HashSet;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.pde.api.tools.internal.model.ProjectComponent;
 import org.eclipse.pde.api.tools.internal.provisional.IApiAnnotations;
 import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers;
@@ -78,7 +79,7 @@
 	}
 
 	@Override
-	public boolean acceptReference(IReference reference) {
+	public boolean acceptReference(IReference reference, IProgressMonitor monitor) {
 		try {
 			IApiMember member = reference.getResolvedReference();
 			if(member != null) {
@@ -133,7 +134,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.tests/src/org/eclipse/pde/api/tools/tests/AbstractApiTest.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/tests/AbstractApiTest.java
index cbc6e7a..9b3ede8 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/tests/AbstractApiTest.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/tests/AbstractApiTest.java
@@ -40,7 +40,13 @@
 import org.eclipse.pde.api.tools.tests.util.ProjectUtils;
 import org.eclipse.pde.api.tools.util.tests.ResourceEventWaiter;
 import org.eclipse.pde.core.plugin.PluginRegistry;
+import org.eclipse.pde.internal.core.ICoreConstants;
+import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.natures.PDE;
+import org.eclipse.pde.ui.tests.util.FreezeMonitor;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
 
 /**
  * Abstract class with commonly used methods for API Tools tests
@@ -61,6 +67,16 @@
 	 */
 	protected static final String TESTING_PLUGIN_PROJECT_NAME = "APIPluginTests"; //$NON-NLS-1$
 
+	@Before
+	public void setUp() throws Exception {
+		FreezeMonitor.expectCompletionInAMinute();
+	}
+
+	@After
+	public void tearDown() throws Exception {
+		FreezeMonitor.done();
+	}
+
 	/**
 	 * Returns the {@link IJavaProject} with the given name. If this method is
 	 * called from a non-plugin unit test, <code>null</code> is always returned.
@@ -209,4 +225,9 @@
 	protected IApiBaseline getWorkspaceBaseline() {
 		return ApiPlugin.getDefault().getApiBaselineManager().getWorkspaceBaseline();
 	}
+
+	@BeforeClass
+	public static void beforeClass() {
+		PDECore.getDefault().getPreferencesManager().setValue(ICoreConstants.RUN_API_ANALYSIS_AS_JOB, false);
+	}
 }
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ApiBaselineManagerTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ApiBaselineManagerTests.java
index d8eabec..3ffdab8 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ApiBaselineManagerTests.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ApiBaselineManagerTests.java
@@ -871,15 +871,19 @@
 		return JavaCore.create(ResourcesPlugin.getWorkspace().getRoot().getProject(TESTING_PLUGIN_PROJECT_NAME));
 	}
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
+		super.setUp();
 		createProject(TESTING_PLUGIN_PROJECT_NAME, new String[] { TESTING_PACKAGE });
 		setPackageToApi(getTestingProject(), TESTING_PACKAGE);
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		deleteProject(TESTING_PLUGIN_PROJECT_NAME);
 		getWorkspaceBaseline().dispose();
+		super.tearDown();
 	}
 }
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ApiDescriptionProcessorTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ApiDescriptionProcessorTests.java
index c9a95e1..fa17fb9 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ApiDescriptionProcessorTests.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ApiDescriptionProcessorTests.java
@@ -182,8 +182,10 @@
 	private static IPath ROOT_PATH = TestSuiteHelper.getPluginDirectoryPath().append("test-source").append("javadoc"); //$NON-NLS-1$ //$NON-NLS-2$
 	private static File componentxml = new File(ROOT_PATH.append("component.xml").toOSString()); //$NON-NLS-1$
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
+		super.setUp();
 		createProject(TESTING_PROJECT_NAME, null);
 		IJavaProject project = getTestingJavaProject(TESTING_PROJECT_NAME);
 		assertNotNull("The java project must have been created", project); //$NON-NLS-1$
@@ -203,9 +205,11 @@
 		performRefactoring(refactoring);
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		deleteProject(TESTING_PROJECT_NAME);
+		super.tearDown();
 	}
 
 	/**
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/HeadlessApiBaselineManagerTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/HeadlessApiBaselineManagerTests.java
index 7340f2b..8565671 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/HeadlessApiBaselineManagerTests.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/HeadlessApiBaselineManagerTests.java
@@ -36,9 +36,11 @@
 
 	private ApiBaselineManager fManager = ApiBaselineManager.getManager();
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		fManager.stop();
+		super.tearDown();
 	}
 
 	/**
@@ -134,7 +136,7 @@
 	/**
 	 * Tests that calling the saving(..) method on the manager in headless mode
 	 * does not fail
-	 * 
+	 *
 	 * @throws CoreException
 	 */
 	@Test
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/PreferencesTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/PreferencesTests.java
index 8f80473..5a8fa17 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/PreferencesTests.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/PreferencesTests.java
@@ -34,8 +34,10 @@
  */
 public class PreferencesTests extends AbstractApiTest {
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
+		super.setUp();
 		IEclipsePreferences inode = InstanceScope.INSTANCE.getNode(ApiPlugin.PLUGIN_ID);
 		assertNotNull("The instance node must exist", inode); //$NON-NLS-1$
 		inode.put(IApiProblemTypes.ILLEGAL_INSTANTIATE, ApiPlugin.VALUE_ERROR);
@@ -52,9 +54,11 @@
 		eprefs.flush();
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		deleteProject(TESTING_PROJECT_NAME);
+		super.tearDown();
 	}
 
 	/**
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ProjectCreationTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ProjectCreationTests.java
index 0137566..f4d407b 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ProjectCreationTests.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/ProjectCreationTests.java
@@ -66,16 +66,20 @@
 		JAVADOC_READ_SRC_DIR = getSourceDirectory(new Path("a").append("b").append("c")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 	}
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
+		super.setUp();
 		createProject(TESTING_PROJECT_NAME, null);
 		IJavaProject project = getTestingJavaProject(TESTING_PROJECT_NAME);
 		assertNotNull("The java project must have been created", project); //$NON-NLS-1$
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		deleteProject(TESTING_PROJECT_NAME);
+		super.tearDown();
 	}
 
 	/**
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/TargetAsBaselineTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/TargetAsBaselineTests.java
index f84314b..a028341 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/TargetAsBaselineTests.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/TargetAsBaselineTests.java
@@ -34,8 +34,10 @@
 public class TargetAsBaselineTests extends AbstractApiTest {
 	ITargetDefinition definition;
 
+	@Override
 	@Before
-	public void setUp() {
+	public void setUp() throws Exception {
+		super.setUp();
 		IPath path = TestSuiteHelper.getPluginDirectoryPath();
 		path = path.append("test-plugins"); //$NON-NLS-1$
 		File file = path.toFile();
diff --git a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/UtilTests.java b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/UtilTests.java
index c3ba11a..4f6e18c 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/UtilTests.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/src/org/eclipse/pde/api/tools/util/tests/UtilTests.java
@@ -452,6 +452,11 @@
 			public IReferenceCollection getExternalDependencies() {
 				return null;
 			}
+
+			@Override
+			public boolean isDisposed() {
+				return false;
+			}
 		}
 		List<IApiComponent> allComponents = new ArrayList<>();
 		String[] componentNames = new String[] {
diff --git a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test4/changed/DebugElement.java b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test4/changed/DebugElement.java
index 4f54d18..fd5c14c 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test4/changed/DebugElement.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test4/changed/DebugElement.java
@@ -158,7 +158,7 @@
 	 * @throws DebugException
 	 */
 	protected void requestFailed(String message, Throwable e) throws DebugException {
-		throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), 
+		throw new DebugException(Status.error(DebugPlugin.getUniqueIdentifier(), 
 				DebugException.TARGET_REQUEST_FAILED, message, e));
 	}
 	
@@ -170,7 +170,7 @@
 	 * @throws DebugException
 	 */
 	protected void notSupported(String message, Throwable e) throws DebugException {
-		throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), 
+		throw new DebugException(Status.error(DebugPlugin.getUniqueIdentifier(), 
 				DebugException.NOT_SUPPORTED, message, e));
 	}
 }
diff --git a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test4/revert/DebugElement.java b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test4/revert/DebugElement.java
index 1302f8e..9723b3a 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test4/revert/DebugElement.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test4/revert/DebugElement.java
@@ -153,7 +153,7 @@
 	 * @throws DebugException
 	 */
 	protected void requestFailed(String message, Throwable e) throws DebugException {
-		throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), 
+		throw new DebugException(Status.error(DebugPlugin.getUniqueIdentifier(), 
 				DebugException.TARGET_REQUEST_FAILED, message, e));
 	}
 	
@@ -165,7 +165,7 @@
 	 * @throws DebugException
 	 */
 	protected void notSupported(String message, Throwable e) throws DebugException {
-		throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), 
+		throw new DebugException(Status.error(DebugPlugin.getUniqueIdentifier(), 
 				DebugException.NOT_SUPPORTED, message, e));
 	}
 }
diff --git a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test5/changed/Breakpoint.java b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test5/changed/Breakpoint.java
index c80282d..29d3fbe 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test5/changed/Breakpoint.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test5/changed/Breakpoint.java
@@ -277,7 +277,7 @@
 	protected IMarker ensureMarker() throws DebugException {
 		IMarker m = getMarker();
 		if (m == null || !m.exists()) {
-			throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED,
+			throw new DebugException(Status.error(DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED,
 				DebugCoreMessages.Breakpoint_no_associated_marker, null)); 
 		}
 		return m;
diff --git a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test5/revert/Breakpoint.java b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test5/revert/Breakpoint.java
index 277d758..2f4479b 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test5/revert/Breakpoint.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test5/revert/Breakpoint.java
@@ -268,7 +268,7 @@
 	protected IMarker ensureMarker() throws DebugException {
 		IMarker m = getMarker();
 		if (m == null || !m.exists()) {
-			throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED,
+			throw new DebugException(Status.error(DebugPlugin.getUniqueIdentifier(), DebugException.REQUEST_FAILED,
 				DebugCoreMessages.Breakpoint_no_associated_marker, null)); 
 		}
 		return m;
diff --git a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test6/changed/RuntimeProcess.java b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test6/changed/RuntimeProcess.java
index fad84a8..710c2b8 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test6/changed/RuntimeProcess.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test6/changed/RuntimeProcess.java
@@ -226,7 +226,7 @@
 				fMonitor.killThread();
 				fMonitor = null;
 			}
-			IStatus status = new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.TARGET_REQUEST_FAILED, DebugCoreMessages.RuntimeProcess_terminate_failed, null);		 
+			IStatus status = Status.error(DebugPlugin.getUniqueIdentifier(), DebugException.TARGET_REQUEST_FAILED, DebugCoreMessages.RuntimeProcess_terminate_failed, null);		 
 			throw new DebugException(status);
 		}
 	}
@@ -363,7 +363,7 @@
 		if (isTerminated()) {
 			return fExitValue;
 		} 
-		throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.TARGET_REQUEST_FAILED, DebugCoreMessages.RuntimeProcess_Exit_value_not_available_until_process_terminates__1, null)); 
+		throw new DebugException(Status.error(DebugPlugin.getUniqueIdentifier(), DebugException.TARGET_REQUEST_FAILED, DebugCoreMessages.RuntimeProcess_Exit_value_not_available_until_process_terminates__1, null)); 
 	}
 	
 	/**
diff --git a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test6/revert/RuntimeProcess.java b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test6/revert/RuntimeProcess.java
index a8df7f5..4f1c806 100644
--- a/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test6/revert/RuntimeProcess.java
+++ b/apitools/org.eclipse.pde.api.tools.tests/test-builder/perf/incremental/test6/revert/RuntimeProcess.java
@@ -223,7 +223,7 @@
 				fMonitor.killThread();
 				fMonitor = null;
 			}
-			IStatus status = new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.TARGET_REQUEST_FAILED, DebugCoreMessages.RuntimeProcess_terminate_failed, null);		 
+			IStatus status = Status.error(DebugPlugin.getUniqueIdentifier(), DebugException.TARGET_REQUEST_FAILED, DebugCoreMessages.RuntimeProcess_terminate_failed, null);		 
 			throw new DebugException(status);
 		}
 	}
@@ -360,7 +360,7 @@
 		if (isTerminated()) {
 			return fExitValue;
 		} 
-		throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugException.TARGET_REQUEST_FAILED, DebugCoreMessages.RuntimeProcess_Exit_value_not_available_until_process_terminates__1, null)); 
+		throw new DebugException(Status.error(DebugPlugin.getUniqueIdentifier(), DebugException.TARGET_REQUEST_FAILED, DebugCoreMessages.RuntimeProcess_Exit_value_not_available_until_process_terminates__1, null)); 
 	}
 	
 	/**
diff --git a/apitools/org.eclipse.pde.api.tools.ui/META-INF/MANIFEST.MF b/apitools/org.eclipse.pde.api.tools.ui/META-INF/MANIFEST.MF
index 2b42e8d..9f87877 100644
--- a/apitools/org.eclipse.pde.api.tools.ui/META-INF/MANIFEST.MF
+++ b/apitools/org.eclipse.pde.api.tools.ui/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.pde.api.tools.ui; singleton:=true
-Bundle-Version: 1.2.500.qualifier
+Bundle-Version: 1.2.600.qualifier
 Bundle-Localization: plugin
 Eclipse-LazyStart: true
 Bundle-ActivationPolicy: lazy
diff --git a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/ApiUIPlugin.java b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/ApiUIPlugin.java
index 136a898..afc96a6 100644
--- a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/ApiUIPlugin.java
+++ b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/ApiUIPlugin.java
@@ -69,10 +69,6 @@
 	private static final String ELCL = ICONS_PATH + "elcl16/"; //basic colors - size 16x16 //$NON-NLS-1$
 
 	/**
-	 * Status code indicating an unexpected internal error.
-	 */
-	public static final int INTERNAL_ERROR = 120;
-	/**
 	 * Relative path to object model icons.
 	 */
 	private final static String OBJECT = ICONS_PATH + "obj16/"; //basic colors - size 16x16 //$NON-NLS-1$
@@ -236,7 +232,7 @@
 	 * @return a new error status
 	 */
 	public static IStatus newErrorStatus(String message, Throwable exception) {
-		return new Status(IStatus.ERROR, getPluginIdentifier(), INTERNAL_ERROR, message, exception);
+		return Status.error(message, exception);
 	}
 
 	private ISessionListener sessionListener = new ISessionListener() {
diff --git a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/actions/ExportSessionAction.java b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/actions/ExportSessionAction.java
index b8113e9..4f3736c 100644
--- a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/actions/ExportSessionAction.java
+++ b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/actions/ExportSessionAction.java
@@ -110,8 +110,7 @@
 						} else {
 							File parent = xmlOutputFile.getParentFile();
 							if (!parent.exists() && !parent.mkdirs()) {
-								return new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID,
-										ActionMessages.ExportSessionAction_failed_to_create_parent_folders);
+								return Status.error(ActionMessages.ExportSessionAction_failed_to_create_parent_folders);
 							}
 						}
 						writer = new BufferedWriter(new FileWriter(xmlOutputFile));
@@ -149,7 +148,7 @@
 							} else {
 								File parent = reportFile.getParentFile();
 								if (!parent.exists() && !parent.mkdirs()) {
-									return new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, ActionMessages.ExportSessionAction_failed_to_create_parent_folders);
+									return Status.error(ActionMessages.ExportSessionAction_failed_to_create_parent_folders);
 								}
 							}
 							writer = new BufferedWriter(new FileWriter(reportFile));
diff --git a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/markers/VersionNumberingResolution.java b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/markers/VersionNumberingResolution.java
index 8934b02..308c38a 100644
--- a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/markers/VersionNumberingResolution.java
+++ b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/markers/VersionNumberingResolution.java
@@ -57,6 +57,7 @@
 			case IApiProblem.MINOR_VERSION_CHANGE_NO_NEW_API:
 				return MarkerMessages.VersionNumberingResolution_minorNoNewAPI0;
 			case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE:
+			case IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE:
 				return MarkerMessages.VersionNumberingResolution_reexportedMajor0;
 			case IApiProblem.MINOR_VERSION_CHANGE_EXECUTION_ENV_CHANGED:
 				return MarkerMessages.VersionNumberingResolution_breeMinor;
@@ -87,6 +88,7 @@
 			case IApiProblem.MINOR_VERSION_CHANGE_NO_NEW_API:
 				return NLS.bind(MarkerMessages.VersionNumberingResolution_minorNoNewAPI1, this.newVersionValue);
 			case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE:
+			case IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE:
 				return NLS.bind(MarkerMessages.VersionNumberingResolution_major1, this.newVersionValue);
 			case IApiProblem.MICRO_VERSION_CHANGE_UNNECESSARILY:
 				return NLS.bind(MarkerMessages.VersionNumberingResolution_unnecessaryMicroIncrease,
@@ -118,6 +120,7 @@
 				title = NLS.bind(MarkerMessages.VersionNumberingResolution_minorNoNewAPI2, this.newVersionValue);
 				break;
 			case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE:
+			case IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE:
 				title = NLS.bind(MarkerMessages.VersionNumberingResolution_major1, this.newVersionValue);
 				break;
 			case IApiProblem.MICRO_VERSION_CHANGE_UNNECESSARILY:
diff --git a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/preferences/ApiErrorsWarningsConfigurationBlock.java b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/preferences/ApiErrorsWarningsConfigurationBlock.java
index e9815bd..8a2b5b8 100644
--- a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/preferences/ApiErrorsWarningsConfigurationBlock.java
+++ b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/preferences/ApiErrorsWarningsConfigurationBlock.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2021 IBM Corporation and others.
+ * Copyright (c) 2007, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -13,7 +13,6 @@
  *******************************************************************************/
 package org.eclipse.pde.api.tools.ui.internal.preferences;
 
-import java.awt.Checkbox;
 import java.net.URL;
 import java.text.MessageFormat;
 import java.util.ArrayList;
@@ -709,7 +708,7 @@
 	private HashMap<Combo, Label> fComboLabelMap = new HashMap<>();
 
 	/**
-	 * Listing of all of the {@link Checkbox}es added to the block
+	 * Listing of all of the {@link Button} with SWT.check added to the block
 	 */
 	private ArrayList<Button> fCheckBoxes = new ArrayList<>();
 
diff --git a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/preferences/ProjectSelectionDialog.java b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/preferences/ProjectSelectionDialog.java
index e4622b4..6293432 100644
--- a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/preferences/ProjectSelectionDialog.java
+++ b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/preferences/ProjectSelectionDialog.java
@@ -18,7 +18,6 @@
 
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.IJavaModel;
 import org.eclipse.jdt.core.IJavaProject;
@@ -175,10 +174,10 @@
 	 */
 	void doSelectionChanged(Object[] objects) {
 		if (objects.length != 1) {
-			updateStatus(new Status(IStatus.ERROR, ApiUIPlugin.getPluginIdentifier(), "")); //$NON-NLS-1$
+			updateStatus(Status.error("")); //$NON-NLS-1$
 			setSelectionResult(null);
 		} else {
-			updateStatus(new Status(IStatus.OK, ApiUIPlugin.getPluginIdentifier(), "")); //$NON-NLS-1$
+			updateStatus(Status.OK_STATUS);
 			setSelectionResult(objects);
 		}
 	}
diff --git a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/use/ApiUseScanJob.java b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/use/ApiUseScanJob.java
index 728626e..ebe1460 100644
--- a/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/use/ApiUseScanJob.java
+++ b/apitools/org.eclipse.pde.api.tools.ui/src/org/eclipse/pde/api/tools/ui/internal/use/ApiUseScanJob.java
@@ -54,7 +54,6 @@
 import org.eclipse.pde.api.tools.internal.search.UseSearchRequestor;
 import org.eclipse.pde.api.tools.internal.search.XmlSearchReporter;
 import org.eclipse.pde.api.tools.internal.util.Util;
-import org.eclipse.pde.api.tools.ui.internal.ApiUIPlugin;
 import org.eclipse.pde.core.target.ITargetDefinition;
 import org.eclipse.pde.core.target.ITargetHandle;
 import org.eclipse.pde.core.target.ITargetPlatformService;
@@ -205,7 +204,7 @@
 	 * @throws CoreException
 	 */
 	void abort(String message) throws CoreException {
-		throw new CoreException(new Status(IStatus.ERROR, ApiUIPlugin.PLUGIN_ID, message));
+		throw new CoreException(Status.error(message));
 	}
 
 	/**
diff --git a/apitools/org.eclipse.pde.api.tools/forceQualifierUpdate.txt b/apitools/org.eclipse.pde.api.tools/forceQualifierUpdate.txt
index 2874595..ed33533 100644
--- a/apitools/org.eclipse.pde.api.tools/forceQualifierUpdate.txt
+++ b/apitools/org.eclipse.pde.api.tools/forceQualifierUpdate.txt
@@ -1,3 +1,4 @@
 # To force a version qualifier update add the bug here
 Bug 566668 - Comparator errors in I20200904-0210
-Bug 577483 - Comparator errors in I20211126-0230
\ No newline at end of file
+Bug 577483 - Comparator errors in I20211126-0230
+Bug 578351 - Lambda generation order is unstable in ecj 
\ No newline at end of file
diff --git a/apitools/org.eclipse.pde.api.tools/pom.xml b/apitools/org.eclipse.pde.api.tools/pom.xml
index 662b02a..872a444 100644
--- a/apitools/org.eclipse.pde.api.tools/pom.xml
+++ b/apitools/org.eclipse.pde.api.tools/pom.xml
@@ -13,10 +13,9 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui.apitools</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.api.tools</artifactId>
   <version>1.2.800-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiAnalysisApplication.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiAnalysisApplication.java
index beee248..287dadb 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiAnalysisApplication.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiAnalysisApplication.java
@@ -113,6 +113,7 @@
 			desc.setAutoBuilding(false);
 			ResourcesPlugin.getWorkspace().setDescription(desc);
 			PDECore.getDefault().getPreferencesManager().setValue(ICoreConstants.DISABLE_API_ANALYSIS_BUILDER, false);
+			PDECore.getDefault().getPreferencesManager().setValue(ICoreConstants.RUN_API_ANALYSIS_AS_JOB, false);
 
 			Request args = Request
 					.readFromArgs((String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS));
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..4a8b6bc 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,14 @@
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
+import java.util.Collections;
 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;
@@ -44,11 +45,13 @@
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
 import org.eclipse.core.runtime.preferences.IPreferencesService;
 import org.eclipse.core.runtime.preferences.IScopeContext;
 import org.eclipse.core.runtime.preferences.InstanceScope;
 import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.pde.api.tools.internal.builder.ApiAnalysisBuilder.ApiAnalysisJob;
 import org.eclipse.pde.api.tools.internal.model.ApiBaseline;
 import org.eclipse.pde.api.tools.internal.model.ApiModelCache;
 import org.eclipse.pde.api.tools.internal.model.ApiModelFactory;
@@ -102,17 +105,17 @@
 	 * The main cache for the manager. The form of the cache is:
 	 *
 	 * <pre>
-	 * HashMap<String(baselineid), {@link IApiBaseline}>
+	 * Map<String(baselineid), {@link IApiBaseline}>
 	 * </pre>
 	 */
-	private HashMap<String, IApiBaseline> baselinecache = null;
+	private volatile ConcurrentHashMap<String, IApiBaseline> baselinecache;
 
 	/**
 	 * 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}
@@ -122,7 +125,7 @@
 	/**
 	 * The current workspace baseline
 	 */
-	private IApiBaseline workspacebaseline = null;
+	private volatile IApiBaseline workspacebaseline;
 
 	/**
 	 * The default save location for persisting the cache from this manager.
@@ -132,7 +135,7 @@
 	/**
 	 * If the cache of baselines needs to be saved or not.
 	 */
-	private boolean fNeedsSaving = false;
+	private volatile boolean fNeedsSaving;
 
 	/**
 	 * The singleton instance
@@ -147,6 +150,7 @@
 			ApiPlugin.getDefault().addSaveParticipant(this);
 			savelocation = ApiPlugin.getDefault().getStateLocation().append(".api_profiles").addTrailingSeparator(); //$NON-NLS-1$
 		}
+		hasinfos = Collections.emptySet();
 	}
 
 	/**
@@ -162,19 +166,22 @@
 	}
 
 	@Override
-	public synchronized IApiBaseline getApiBaseline(String name) {
+	public IApiBaseline getApiBaseline(String name) {
+		if (name == null) {
+			return null;
+		}
 		initializeStateCache();
 		return baselinecache.get(name);
 	}
 
 	@Override
-	public synchronized IApiBaseline[] getApiBaselines() {
+	public IApiBaseline[] getApiBaselines() {
 		initializeStateCache();
-		return baselinecache.values().toArray(new IApiBaseline[baselinecache.size()]);
+		return baselinecache.values().toArray(new IApiBaseline[0]);
 	}
 
 	@Override
-	public synchronized void addApiBaseline(IApiBaseline newbaseline) {
+	public void addApiBaseline(IApiBaseline newbaseline) {
 		if (newbaseline != null) {
 			initializeStateCache();
 			baselinecache.put(newbaseline.getName(), newbaseline);
@@ -186,33 +193,36 @@
 	}
 
 	@Override
-	public synchronized boolean removeApiBaseline(String name) {
-		if (name != null) {
-			initializeStateCache();
-			IApiBaseline baseline = baselinecache.remove(name);
-			if (baseline != null) {
-				baseline.dispose();
-				boolean success = true;
-				if (savelocation == null) {
-					return success;
-				}
-				// remove from filesystem
-				File file = savelocation.append(name + BASELINE_FILE_EXTENSION).toFile();
-				if (file.exists()) {
-					try {
-						success &= Files.deleteIfExists(file.toPath());
-					} catch (IOException e) {
-						ApiPlugin.log(e);
-					}
-				}
-				fNeedsSaving = true;
-
-				// flush the model cache
-				ApiModelCache.getCache().removeElementInfo(baseline);
+	public boolean removeApiBaseline(String name) {
+		if (name == null) {
+			return false;
+		}
+		initializeStateCache();
+		IApiBaseline baseline = baselinecache.remove(name);
+		if (baseline == null) {
+			return false;
+		}
+		synchronized (this) {
+			baseline.dispose();
+			boolean success = true;
+			if (savelocation == null) {
 				return success;
 			}
+			// remove from filesystem
+			File file = savelocation.append(name + BASELINE_FILE_EXTENSION).toFile();
+			if (file.exists()) {
+				try {
+					success &= Files.deleteIfExists(file.toPath());
+				} catch (IOException e) {
+					ApiPlugin.log(e);
+				}
+			}
+			fNeedsSaving = true;
+
+			// flush the model cache
+			ApiModelCache.getCache().removeElementInfo(baseline);
+			return success;
 		}
-		return false;
 	}
 
 	/**
@@ -222,9 +232,9 @@
 	 * @param baseline the given baseline
 	 * @throws CoreException if an exception occurs while loading baseline infos
 	 */
-	public void loadBaselineInfos(IApiBaseline baseline) throws CoreException {
+	public void loadBaselineInfos(ApiBaseline baseline) throws CoreException {
 		initializeStateCache();
-		if (hasinfos.contains(baseline.getName())) {
+		if (isBaselineLoaded(baseline)) {
 			return;
 		}
 		String filename = handlecache.get(baseline.getName());
@@ -232,7 +242,7 @@
 			File file = new File(filename);
 			if (file.exists()) {
 				try (FileInputStream inputStream = new FileInputStream(file)) {
-					restoreBaseline(baseline, inputStream);
+					baseline.restoreFrom(inputStream);
 				} catch (IOException e) {
 					ApiPlugin.log(e);
 				}
@@ -241,6 +251,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
@@ -248,31 +262,48 @@
 	 * @throws FactoryConfigurationError
 	 * @throws ParserConfigurationException
 	 */
-	private synchronized void initializeStateCache() {
-		long time = System.currentTimeMillis();
-		if (baselinecache == null) {
-			handlecache = new HashMap<>(8);
-			hasinfos = new HashSet<>(8);
-			baselinecache = new LinkedHashMap<>(8);
-			if (!ApiPlugin.isRunningInFramework()) {
-				return;
-			}
-			File[] baselines = savelocation.toFile().listFiles((FileFilter) pathname -> pathname.getName().endsWith(BASELINE_FILE_EXTENSION));
-			if (baselines != null) {
-				IApiBaseline newbaseline = null;
-				for (File baseline : baselines) {
-					if (baseline.exists()) {
-						newbaseline = new ApiBaseline(new Path(baseline.getName()).removeFileExtension().toString());
-						handlecache.put(newbaseline.getName(), baseline.getAbsolutePath());
-						baselinecache.put(newbaseline.getName(), newbaseline);
-					}
+	private void initializeStateCache() {
+		if (baselinecache != null) {
+			return;
+		}
+		if (!ApiPlugin.isRunningInFramework()) {
+			synchronized (this) {
+				if (baselinecache == null) {
+					handlecache = new ConcurrentHashMap<>(8);
+					hasinfos = ConcurrentHashMap.newKeySet(8);
+					baselinecache = new ConcurrentHashMap<>(8);
 				}
 			}
-			String def = getDefaultProfilePref();
-			IApiBaseline baseline = baselinecache.get(def);
-			defaultbaseline = (baseline != null ? def : null);
-			if (ApiPlugin.DEBUG_BASELINE_MANAGER) {
-				System.out.println("Time to initialize state cache: " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+			return;
+		}
+
+		long time = System.currentTimeMillis();
+		synchronized (this) {
+			if (baselinecache == null) {
+				handlecache = new ConcurrentHashMap<>(8);
+				hasinfos = ConcurrentHashMap.newKeySet(8);
+				ConcurrentHashMap<String, IApiBaseline> bcache = new ConcurrentHashMap<>(8);
+				File[] baselines = savelocation.toFile().listFiles((FileFilter) pathname -> pathname.getName().endsWith(BASELINE_FILE_EXTENSION));
+				if (baselines != null) {
+					IApiBaseline newbaseline = null;
+					for (File baseline : baselines) {
+						if (baseline.exists()) {
+							newbaseline = new ApiBaseline(new Path(baseline.getName()).removeFileExtension().toString());
+							handlecache.put(newbaseline.getName(), baseline.getAbsolutePath());
+							bcache.put(newbaseline.getName(), newbaseline);
+						}
+					}
+				}
+				String def = getDefaultProfilePref();
+				if (def != null && bcache.get(def) != null) {
+					defaultbaseline = def;
+				} else {
+					defaultbaseline = null;
+				}
+				baselinecache = bcache;
+				if (ApiPlugin.DEBUG_BASELINE_MANAGER) {
+					System.out.println("Time to initialize state cache: " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+				}
 			}
 		}
 	}
@@ -302,14 +333,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();
@@ -410,12 +441,14 @@
 	 * Restore a baseline from the given input stream (persisted baseline).
 	 *
 	 * @param baseline the given baseline to restore
-	 * @param stream the given input stream
+	 * @param stream   the given input stream
 	 * @throws CoreException if unable to restore the baseline
+	 * @return restored baseline components or null if restore didn't work
 	 */
-	private void restoreBaseline(IApiBaseline baseline, InputStream stream) throws CoreException {
+	public IApiComponent[] readBaselineComponents(ApiBaseline baseline, InputStream stream) throws CoreException {
 		long start = System.currentTimeMillis();
 		DocumentBuilder parser = null;
+		IApiComponent[] restored = null;
 		try {
 			parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 			parser.setErrorHandler(new DefaultHandler());
@@ -472,23 +505,15 @@
 						}
 					}
 				}
-				baseline.addApiComponents(components.toArray(new IApiComponent[components.size()]));
+				restored = components.toArray(new IApiComponent[components.size()]);
 			}
 		} catch (IOException | SAXException e) {
 			abort("Error restoring API baseline", e); //$NON-NLS-1$
-		} finally {
-			try {
-				stream.close();
-			} catch (IOException io) {
-				ApiPlugin.log(io);
-			}
-		}
-		if (baseline == null) {
-			abort("Invalid baseline description", null); //$NON-NLS-1$
 		}
 		if (ApiPlugin.DEBUG_BASELINE_MANAGER) {
 			System.out.println("Time to restore a persisted baseline : " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
 		}
+		return restored;
 	}
 
 	@Override
@@ -531,7 +556,7 @@
 	 *         otherwise
 	 */
 	public boolean isExistingProfileName(String name) {
-		if (baselinecache == null) {
+		if (baselinecache == null || name == null) {
 			return false;
 		}
 		return baselinecache.containsKey(name);
@@ -542,6 +567,7 @@
 	 */
 	public void stop() {
 		try {
+			Job.getJobManager().cancel(ApiAnalysisJob.class);
 			if (baselinecache != null) {
 				// we should first dispose all existing baselines
 				for (IApiBaseline iApiBaseline : baselinecache.values()) {
@@ -557,7 +583,7 @@
 			if (handlecache != null) {
 				handlecache.clear();
 			}
-			if (hasinfos != null) {
+			if (!hasinfos.isEmpty()) {
 				hasinfos.clear();
 			}
 			StubApiComponent.disposeAllCaches();
@@ -584,9 +610,13 @@
 	}
 
 	@Override
-	public synchronized IApiBaseline getDefaultApiBaseline() {
+	public IApiBaseline getDefaultApiBaseline() {
 		initializeStateCache();
-		return baselinecache.get(defaultbaseline);
+		String defbaseline = defaultbaseline;
+		if (defbaseline == null) {
+			return null;
+		}
+		return baselinecache.get(defbaseline);
 	}
 
 	@Override
@@ -596,32 +626,46 @@
 	}
 
 	@Override
-	public synchronized IApiBaseline getWorkspaceBaseline() {
-		if (ApiPlugin.isRunningInFramework()) {
-			if (this.workspacebaseline == null) {
-				try {
-					this.workspacebaseline = createWorkspaceBaseline();
-				} catch (CoreException e) {
-					ApiPlugin.log(e);
-				}
-			}
-			return this.workspacebaseline;
+	public IApiBaseline getWorkspaceBaseline() {
+		if (!ApiPlugin.isRunningInFramework()) {
+			return null;
 		}
-		return null;
+		if (this.workspacebaseline == null) {
+			try {
+				synchronized (this) {
+					if (this.workspacebaseline == null) {
+						this.workspacebaseline = createWorkspaceBaseline();
+					}
+				}
+			} catch (CoreException e) {
+				ApiPlugin.log(e);
+			}
+		}
+		return this.workspacebaseline;
 	}
 
 	/**
 	 * Disposes the workspace baseline such that a new one will be created on
 	 * the next request.
 	 */
-	synchronized void disposeWorkspaceBaseline() {
-		if (workspacebaseline != null) {
-			if (ApiPlugin.DEBUG_BASELINE_MANAGER) {
-				System.out.println("disposing workspace baseline"); //$NON-NLS-1$
+	void disposeWorkspaceBaseline() {
+		if (workspacebaseline == null) {
+			return;
+		}
+		Job.getJobManager().cancel(ApiAnalysisJob.class);
+		IApiBaseline oldBaseline = null;
+		synchronized (this) {
+			if (workspacebaseline != null) {
+				if (ApiPlugin.DEBUG_BASELINE_MANAGER) {
+					System.out.println("disposing workspace baseline"); //$NON-NLS-1$
+				}
+				oldBaseline = workspacebaseline;
+				StubApiComponent.disposeAllCaches();
+				workspacebaseline = null;
 			}
-			workspacebaseline.dispose();
-			StubApiComponent.disposeAllCaches();
-			workspacebaseline = null;
+		}
+		if (oldBaseline != null) {
+			oldBaseline.dispose();
 		}
 	}
 
@@ -680,7 +724,7 @@
 	}
 
 	@Override
-	public synchronized IApiComponent getWorkspaceComponent(String symbolicName) {
+	public IApiComponent getWorkspaceComponent(String symbolicName) {
 		IApiBaseline baseline = getWorkspaceBaseline();
 		if (baseline != null) {
 			return baseline.getApiComponent(symbolicName);
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiDescription.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiDescription.java
index 3dad9d1..84a3f78 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiDescription.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ApiDescription.java
@@ -69,7 +69,7 @@
 	/**
 	 * Whether this description needs saving
 	 */
-	private boolean fModified = false;
+	private volatile boolean fModified;
 
 	/**
 	 * A comparator for {@link ManifestNode}s. Used while visiting child nodes
@@ -559,7 +559,7 @@
 			node.restrictions = restrictions;
 			return Status.OK_STATUS;
 		}
-		return new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, ELEMENT_NOT_FOUND, MessageFormat.format("Failed to set API restriction: {0} not found in {1}", element.toString(), fOwningComponentId), null); //$NON-NLS-1$
+		return Status.error(MessageFormat.format("Failed to set API restriction: {0} not found in {1}", element, fOwningComponentId), null); //$NON-NLS-1$
 	}
 
 	@Override
@@ -578,7 +578,7 @@
 			node.visibility = visibility;
 			return Status.OK_STATUS;
 		}
-		return new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, ELEMENT_NOT_FOUND, MessageFormat.format("Failed to set API visibility: {0} not found in {1}", element.toString(), fOwningComponentId), null);//$NON-NLS-1$
+		return Status.error(MessageFormat.format("Failed to set API visibility: {0} not found in {1}", element, fOwningComponentId), null);//$NON-NLS-1$
 	}
 
 	@Override
@@ -634,7 +634,7 @@
 	/**
 	 * Marks the description as modified
 	 */
-	protected synchronized void modified() {
+	protected void modified() {
 		fModified = true;
 	}
 
@@ -643,7 +643,7 @@
 	 *
 	 * @return
 	 */
-	protected synchronized boolean isModified() {
+	protected boolean isModified() {
 		return fModified;
 	}
 
@@ -653,7 +653,7 @@
 	 * @param mod
 	 * @return
 	 */
-	protected synchronized void setModified(boolean mod) {
+	protected void setModified(boolean mod) {
 		fModified = mod;
 	}
 
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/FilterStore.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/FilterStore.java
index 032792a..87a7ac3 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/FilterStore.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/FilterStore.java
@@ -173,7 +173,7 @@
 			} catch (IOException e) {
 				ApiPlugin.log(e);
 			} finally {
-				fComponent.closingZipFileAndStream(filterstream, jarFile);
+				BundleComponent.closingZipFileAndStream(filterstream, jarFile);
 			}
 		}
 	}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ProjectApiDescription.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ProjectApiDescription.java
index 0e0e509..3b5475f 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ProjectApiDescription.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/ProjectApiDescription.java
@@ -26,7 +26,6 @@
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.IClassFile;
@@ -78,7 +77,7 @@
 	/**
 	 * Whether a package refresh is in progress
 	 */
-	private boolean fRefreshingInProgress = false;
+	private volatile boolean fRefreshingInProgress;
 
 	/**
 	 * Associated manifest file
@@ -91,7 +90,7 @@
 	 * traversing the cached nodes, rather than traversing the java model
 	 * elements (effectively building the cache).
 	 */
-	private boolean fInSynch = false;
+	private volatile boolean fInSynch;
 
 	/**
 	 * A node for a package.
@@ -169,7 +168,7 @@
 
 		long fTimeStamp = -1L;
 		long fBuildStamp = -1L;
-		private boolean fRefreshing = false;
+		private volatile boolean fRefreshing;
 
 		IType fType;
 
@@ -192,114 +191,124 @@
 		}
 
 		@Override
-		protected synchronized ManifestNode refresh() {
+		protected ManifestNode refresh() {
 			if (fRefreshing) {
-				if (ApiPlugin.DEBUG_API_DESCRIPTION) {
-					StringBuilder buffer = new StringBuilder();
-					buffer.append("Refreshing manifest node: "); //$NON-NLS-1$
-					buffer.append(this);
-					buffer.append(" aborted because a refresh is already in progress"); //$NON-NLS-1$
-					System.out.println(buffer.toString());
-				}
+				logRefreshing();
 				return this;
 			}
-			try {
-				fRefreshing = true;
-				int parentVis = resolveVisibility(parent);
-				if (VisibilityModifiers.isAPI(parentVis)) {
-					ICompilationUnit unit = fType.getCompilationUnit();
-					if (unit != null) {
-						IResource resource = null;
-						try {
-							resource = unit.getUnderlyingResource();
-						} catch (JavaModelException e) {
-							if (ApiPlugin.DEBUG_API_DESCRIPTION) {
-								StringBuilder buffer = new StringBuilder();
-								buffer.append("Failed to get underlying resource for compilation unit: "); //$NON-NLS-1$
-								buffer.append(unit);
-								System.out.println(buffer.toString());
-							}
-							// exception if the resource does not exist
-							if (!e.getJavaModelStatus().isDoesNotExist()) {
-								ApiPlugin.log(e.getStatus());
-								return this;
-							}
-						}
-						if (resource != null && resource.exists()) {
-							long stamp = resource.getModificationStamp();
-							if (stamp != fTimeStamp) {
-								// compute current CRC
-								CRCVisitor visitor = new CRCVisitor();
-								visitType(this, visitor);
-								long crc = visitor.getValue();
+			synchronized (this) {
+				if (fRefreshing) {
+					logRefreshing();
+					return this;
+				}
+				try {
+					fRefreshing = true;
+					int parentVis = resolveVisibility(parent);
+					if (VisibilityModifiers.isAPI(parentVis)) {
+						ICompilationUnit unit = fType.getCompilationUnit();
+						if (unit != null) {
+							IResource resource = null;
+							try {
+								resource = unit.getUnderlyingResource();
+							} catch (JavaModelException e) {
 								if (ApiPlugin.DEBUG_API_DESCRIPTION) {
 									StringBuilder buffer = new StringBuilder();
-									buffer.append("Resource has changed for type manifest node: "); //$NON-NLS-1$
-									buffer.append(this);
-									buffer.append(" tag scanning the new type"); //$NON-NLS-1$
-									buffer.append(" (CRC "); //$NON-NLS-1$
-									buffer.append(crc);
-									buffer.append(')');
+									buffer.append("Failed to get underlying resource for compilation unit: "); //$NON-NLS-1$
+									buffer.append(unit);
 									System.out.println(buffer.toString());
 								}
-								modified();
-								children.clear();
-								restrictions = RestrictionModifiers.NO_RESTRICTIONS;
-								fTimeStamp = resource.getModificationStamp();
-								try {
-									TagScanner.newScanner().scan(unit, ProjectApiDescription.this, getApiTypeContainer((IPackageFragmentRoot) fType.getPackageFragment().getParent()), null);
-								} catch (CoreException e) {
+								// exception if the resource does not exist
+								if (!e.getJavaModelStatus().isDoesNotExist()) {
 									ApiPlugin.log(e.getStatus());
+									return this;
 								}
-								// see if the description changed
-								visitor = new CRCVisitor();
-								visitType(this, visitor);
-								long crc2 = visitor.getValue();
-								if (crc != crc2) {
-									// update relative build time stamp
-									fBuildStamp = BuildStamps.getBuildStamp(resource.getProject());
+							}
+							if (resource != null && resource.exists()) {
+								long stamp = resource.getModificationStamp();
+								if (stamp != fTimeStamp) {
+									// compute current CRC
+									CRCVisitor visitor = new CRCVisitor();
+									visitType(this, visitor);
+									long crc = visitor.getValue();
 									if (ApiPlugin.DEBUG_API_DESCRIPTION) {
 										StringBuilder buffer = new StringBuilder();
-										buffer.append("CRC changed for type manifest node: "); //$NON-NLS-1$
+										buffer.append("Resource has changed for type manifest node: "); //$NON-NLS-1$
 										buffer.append(this);
+										buffer.append(" tag scanning the new type"); //$NON-NLS-1$
 										buffer.append(" (CRC "); //$NON-NLS-1$
-										buffer.append(crc2);
+										buffer.append(crc);
 										buffer.append(')');
 										System.out.println(buffer.toString());
 									}
+									modified();
+									children.clear();
+									restrictions = RestrictionModifiers.NO_RESTRICTIONS;
+									fTimeStamp = resource.getModificationStamp();
+									try {
+										TagScanner.newScanner().scan(unit, ProjectApiDescription.this, getApiTypeContainer((IPackageFragmentRoot) fType.getPackageFragment().getParent()), null);
+									} catch (CoreException e) {
+										ApiPlugin.log(e.getStatus());
+									}
+									// see if the description changed
+									visitor = new CRCVisitor();
+									visitType(this, visitor);
+									long crc2 = visitor.getValue();
+									if (crc != crc2) {
+										// update relative build time stamp
+										fBuildStamp = BuildStamps.getBuildStamp(resource.getProject());
+										if (ApiPlugin.DEBUG_API_DESCRIPTION) {
+											StringBuilder buffer = new StringBuilder();
+											buffer.append("CRC changed for type manifest node: "); //$NON-NLS-1$
+											buffer.append(this);
+											buffer.append(" (CRC "); //$NON-NLS-1$
+											buffer.append(crc2);
+											buffer.append(')');
+											System.out.println(buffer.toString());
+										}
+									}
 								}
+							} else {
+								if (ApiPlugin.DEBUG_API_DESCRIPTION) {
+									StringBuilder buffer = new StringBuilder();
+									buffer.append("Underlying resource for the type manifest node: "); //$NON-NLS-1$
+									buffer.append(this);
+									buffer.append(" does not exist or is null"); //$NON-NLS-1$
+									System.out.println(buffer.toString());
+								}
+								// element has been removed
+								modified();
+								parent.children.remove(element);
+								return null;
 							}
 						} else {
 							if (ApiPlugin.DEBUG_API_DESCRIPTION) {
 								StringBuilder buffer = new StringBuilder();
-								buffer.append("Underlying resource for the type manifest node: "); //$NON-NLS-1$
+								buffer.append("Failed to look up compilation unit for "); //$NON-NLS-1$
+								buffer.append(fType);
+								buffer.append(" refreshing type manifest node: "); //$NON-NLS-1$
 								buffer.append(this);
-								buffer.append(" does not exist or is null"); //$NON-NLS-1$
 								System.out.println(buffer.toString());
 							}
-							// element has been removed
-							modified();
-							parent.children.remove(element);
-							return null;
+							// TODO: binary type
 						}
 					} else {
-						if (ApiPlugin.DEBUG_API_DESCRIPTION) {
-							StringBuilder buffer = new StringBuilder();
-							buffer.append("Failed to look up compilation unit for "); //$NON-NLS-1$
-							buffer.append(fType);
-							buffer.append(" refreshing type manifest node: "); //$NON-NLS-1$
-							buffer.append(this);
-							System.out.println(buffer.toString());
-						}
-						// TODO: binary type
+						// don't scan internal types
 					}
-				} else {
-					// don't scan internal types
+				} finally {
+					fRefreshing = false;
 				}
-			} finally {
-				fRefreshing = false;
+				return this;
 			}
-			return this;
+		}
+
+		private void logRefreshing() {
+			if (ApiPlugin.DEBUG_API_DESCRIPTION) {
+				StringBuilder buffer = new StringBuilder();
+				buffer.append("Refreshing manifest node: "); //$NON-NLS-1$
+				buffer.append(this);
+				buffer.append(" aborted because a refresh is already in progress"); //$NON-NLS-1$
+				System.out.println(buffer.toString());
+			}
 		}
 
 		@Override
@@ -556,47 +565,57 @@
 	/**
 	 * Refreshes package nodes if required.
 	 */
-	synchronized void refreshPackages() {
+	void refreshPackages() {
 		if (fRefreshingInProgress) {
-			if (ApiPlugin.DEBUG_API_DESCRIPTION) {
-				StringBuilder buffer = new StringBuilder();
-				buffer.append("Refreshing manifest node: "); //$NON-NLS-1$
-				buffer.append(this);
-				buffer.append(" aborted because a refresh is already in progress"); //$NON-NLS-1$
-				System.out.println(buffer.toString());
-			}
+			logPackafesRefresh();
 			return;
 		}
-		// check if in synch
-		if (fManifestFile == null || (fManifestFile.getModificationStamp() != fPackageTimeStamp)) {
-			try {
-				modified();
-				fRefreshingInProgress = true;
-				// set all existing packages to PRIVATE (could clear
-				// the map, but it would be less efficient)
-				Iterator<ManifestNode> iterator = fPackageMap.values().iterator();
-				while (iterator.hasNext()) {
-					PackageNode node = (PackageNode) iterator.next();
-					node.visibility = VisibilityModifiers.PRIVATE;
-				}
-				fManifestFile = getJavaProject().getProject().getFile(JarFile.MANIFEST_NAME);
-				if (fManifestFile.exists()) {
-					try {
-						IPackageFragment[] fragments = getLocalPackageFragments();
-						Set<String> names = new HashSet<>();
-						for (IPackageFragment fragment : fragments) {
-							names.add(fragment.getElementName());
-						}
-						ProjectComponent component = getApiComponent();
-						BundleComponent.initializeApiDescription(this, component.getBundleDescription(), names);
-						fPackageTimeStamp = fManifestFile.getModificationStamp();
-					} catch (CoreException e) {
-						ApiPlugin.log(e.getStatus());
-					}
-				}
-			} finally {
-				fRefreshingInProgress = false;
+		synchronized (this) {
+			if (fRefreshingInProgress) {
+				logPackafesRefresh();
+				return;
 			}
+			// check if in synch
+			if (fManifestFile == null || (fManifestFile.getModificationStamp() != fPackageTimeStamp)) {
+				try {
+					modified();
+					fRefreshingInProgress = true;
+					// set all existing packages to PRIVATE (could clear
+					// the map, but it would be less efficient)
+					Iterator<ManifestNode> iterator = fPackageMap.values().iterator();
+					while (iterator.hasNext()) {
+						PackageNode node = (PackageNode) iterator.next();
+						node.visibility = VisibilityModifiers.PRIVATE;
+					}
+					fManifestFile = getJavaProject().getProject().getFile(JarFile.MANIFEST_NAME);
+					if (fManifestFile.exists()) {
+						try {
+							IPackageFragment[] fragments = getLocalPackageFragments();
+							Set<String> names = new HashSet<>();
+							for (IPackageFragment fragment : fragments) {
+								names.add(fragment.getElementName());
+							}
+							ProjectComponent component = getApiComponent();
+							BundleComponent.initializeApiDescription(this, component.getBundleDescription(), names);
+							fPackageTimeStamp = fManifestFile.getModificationStamp();
+						} catch (CoreException e) {
+							ApiPlugin.log(e.getStatus());
+						}
+					}
+				} finally {
+					fRefreshingInProgress = false;
+				}
+			}
+		}
+	}
+
+	private void logPackafesRefresh() {
+		if (ApiPlugin.DEBUG_API_DESCRIPTION) {
+			StringBuilder buffer = new StringBuilder();
+			buffer.append("Refreshing manifest node: "); //$NON-NLS-1$
+			buffer.append(this);
+			buffer.append(" aborted because a refresh is already in progress"); //$NON-NLS-1$
+			System.out.println(buffer.toString());
 		}
 	}
 
@@ -630,7 +649,7 @@
 	synchronized IApiTypeContainer getApiTypeContainer(IPackageFragmentRoot root) throws CoreException {
 		IApiTypeContainer container = getApiComponent().getTypeContainer(root);
 		if (container == null) {
-			throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, "Unable to resolve type conatiner for package fragment root")); //$NON-NLS-1$
+			throw new CoreException(Status.error("Unable to resolve type conatiner for package fragment root")); //$NON-NLS-1$
 		}
 		return container;
 	}
@@ -705,7 +724,7 @@
 	 * Notes that the underlying project has changed in some way and that the
 	 * description cache is no longer in synch with the project.
 	 */
-	public synchronized void projectChanged() {
+	public void projectChanged() {
 		fInSynch = false;
 	}
 
@@ -737,7 +756,7 @@
 		IApiBaseline baseline = ApiBaselineManager.getManager().getWorkspaceBaseline();
 		ProjectComponent component = (ProjectComponent) baseline.getApiComponent(getJavaProject().getProject());
 		if (component == null) {
-			throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, "Unable to resolve project API component for API description")); //$NON-NLS-1$
+			throw new CoreException(Status.error("Unable to resolve project API component for API description")); //$NON-NLS-1$
 		}
 		return component;
 	}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/SynchronizedOverflowingLRUCache.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/SynchronizedOverflowingLRUCache.java
new file mode 100644
index 0000000..13336bd
--- /dev/null
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/SynchronizedOverflowingLRUCache.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Andrey Loskutov and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Andrey Loskutov (loskutov@gmx.de) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.api.tools.internal;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.OverflowingLRUCache;
+
+/**
+ * The partly synchronized variant of {@link OverflowingLRUCache}. Only
+ * public/protected methods overridden in this class are synchronized.
+ */
+public abstract class SynchronizedOverflowingLRUCache<K, V> extends OverflowingLRUCache<K, V> {
+
+	public SynchronizedOverflowingLRUCache(int size) {
+		super(size);
+	}
+
+	public SynchronizedOverflowingLRUCache(int size, int overflow) {
+		super(size, overflow);
+	}
+
+	/**
+	 * Returns if the cache has any elements in it or not
+	 *
+	 * @return true if the cache has no entries, false otherwise
+	 */
+	public synchronized boolean isEmpty() {
+		return !super.keys().hasMoreElements();
+	}
+
+	@Override
+	public synchronized V put(K key, V value) {
+		return super.put(key, value);
+	}
+
+	@Override
+	public synchronized V get(K key) {
+		return super.get(key);
+	}
+
+	@Override
+	public synchronized void flush() {
+		super.flush();
+	}
+
+	@Override
+	public synchronized V remove(K key) {
+		return super.remove(key);
+	}
+
+	/**
+	 * @deprecated Should not be used, because iteration over keys is not guaranteed
+	 *             to return valid results if the cache is structurally modified
+	 *             while enumerating. Use {@link #keysSnapshot()} instead.
+	 */
+	@Deprecated
+	@Override
+	public synchronized Enumeration<K> keys() {
+		return super.keys();
+	}
+
+	/**
+	 * @return MT-safe snapshot of the keys in the cache.
+	 */
+	public synchronized List<K> keysSnapshot() {
+		Enumeration<K> keys = super.keys();
+		return Collections.list(keys);
+	}
+
+	/**
+	 * @deprecated Should not be used, because iteration over elements is not
+	 *             guaranteed to return valid results if the cache is structurally
+	 *             modified while enumerating. Use {@link #elementsSnapshot()}
+	 *             instead.
+	 */
+	@Deprecated
+	@Override
+	public synchronized Enumeration<V> elements() {
+		return super.elements();
+	}
+
+	/**
+	 * @return MT-safe snapshot of the elements in the cache.
+	 */
+	public synchronized List<V> elementsSnapshot() {
+		return Collections.list(super.elements());
+	}
+
+	@Override
+	public synchronized void setSpaceLimit(int limit) {
+		super.setSpaceLimit(limit);
+	}
+
+}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractIllegalMethodReference.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractIllegalMethodReference.java
index fd9b911..e0fd224 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractIllegalMethodReference.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractIllegalMethodReference.java
@@ -17,6 +17,7 @@
 import java.util.Map;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.pde.api.tools.internal.model.MethodKey;
 import org.eclipse.pde.api.tools.internal.provisional.builder.IReference;
 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor;
@@ -58,9 +59,9 @@
 	}
 
 	@Override
-	public boolean considerReference(IReference reference) {
+	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
 		MethodKey key = new MethodKey(reference.getReferencedTypeName(), reference.getReferencedMemberName(), reference.getReferencedSignature(), true);
-		if (super.considerReference(reference) && fIllegalMethods.containsKey(key)) {
+		if (super.considerReference(reference, monitor) && fIllegalMethods.containsKey(key)) {
 			retainReference(reference);
 			return true;
 		}
@@ -76,7 +77,7 @@
 				if (member instanceof IApiMethod) {
 					IApiMethod method = (IApiMethod) member;
 					if (method.isDefaultMethod()) {
-						return considerReference(reference);
+						return considerReference(reference, monitor);
 					}
 				}
 			}
@@ -85,8 +86,8 @@
 	}
 
 	@Override
-	protected boolean isProblem(IReference reference) {
-		if (!super.isProblem(reference)) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
+		if (!super.isProblem(reference, monitor)) {
 			return false;
 		}
 		IApiMember method = reference.getResolvedReference();
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractIllegalTypeReference.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractIllegalTypeReference.java
index c126ea1..499c084 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractIllegalTypeReference.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractIllegalTypeReference.java
@@ -17,6 +17,7 @@
 import java.util.Map;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jdt.core.ICompilationUnit;
 import org.eclipse.jdt.core.IMethod;
 import org.eclipse.jdt.core.ISourceRange;
@@ -61,8 +62,8 @@
 	}
 
 	@Override
-	public boolean considerReference(IReference reference) {
-		if (super.considerReference(reference) && fIllegalTypes.containsKey(reference.getReferencedTypeName())) {
+	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
+		if (super.considerReference(reference, monitor) && fIllegalTypes.containsKey(reference.getReferencedTypeName())) {
 			retainReference(reference);
 			return true;
 		}
@@ -80,8 +81,8 @@
 	}
 
 	@Override
-	protected boolean isProblem(IReference reference) {
-		if (!super.isProblem(reference)) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
+		if (!super.isProblem(reference, monitor)) {
 			return false;
 		}
 		IApiMember type = reference.getResolvedReference();
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractProblemDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractProblemDetector.java
index b470445..9523c6d 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractProblemDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractProblemDetector.java
@@ -54,6 +54,7 @@
 import org.eclipse.pde.api.tools.internal.provisional.IApiMarkerConstants;
 import org.eclipse.pde.api.tools.internal.provisional.builder.IApiProblemDetector;
 import org.eclipse.pde.api.tools.internal.provisional.builder.IReference;
+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.IApiElement;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiField;
@@ -159,7 +160,7 @@
 	}
 
 	@Override
-	public boolean considerReference(IReference reference) {
+	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
 		return reference != null && (reference.getReferenceKind() & getReferenceKinds()) > 0;
 	}
 
@@ -506,16 +507,16 @@
 		List<IApiProblem> problems = new LinkedList<>();
 		Iterator<IReference> iterator = references.iterator();
 		SubMonitor loopMonitor = SubMonitor.convert(monitor, references.size());
-		while (iterator.hasNext()) {
+		while (iterator.hasNext() && !monitor.isCanceled()) {
 			loopMonitor.split(1);
 			IReference reference = iterator.next();
 			if (reference.getResolvedReference() == null) {
 				// unresolved reference ignore it
 			} else {
-				if (isProblem(reference)) {
+				if (isProblem(reference, monitor)) {
+					IApiComponent component = reference.getMember().getApiComponent();
 					try {
 						IApiProblem problem = null;
-						IApiComponent component = reference.getMember().getApiComponent();
 						if (component instanceof ProjectComponent) {
 							ProjectComponent ppac = (ProjectComponent) component;
 							IJavaProject project = ppac.getJavaProject();
@@ -528,6 +529,7 @@
 						}
 					} catch (CoreException e) {
 						ApiPlugin.log(e.getStatus());
+						checkIfDisposed(component, monitor);
 					}
 				}
 			}
@@ -536,12 +538,37 @@
 	}
 
 	/**
+	 * Checks if given component is disposed or belongs to already disposed baseline
+	 * - and if yes, cancels given monitor if the API analysis runs in a job
+	 *
+	 * @param component
+	 * @param monitor
+	 */
+	public static void checkIfDisposed(IApiComponent component, IProgressMonitor monitor) {
+		if (component != null && !monitor.isCanceled() && ApiAnalysisBuilder.isRunningAsJob()) {
+			try {
+				if (component.isDisposed()) {
+					monitor.setCanceled(true);
+					return;
+				}
+				IApiBaseline baseline = component.getBaseline();
+				if (baseline != null && baseline.isDisposed()) {
+					monitor.setCanceled(true);
+				}
+			} catch (CoreException e) {
+				monitor.setCanceled(true);
+			}
+		}
+	}
+
+	/**
 	 * Returns whether the resolved reference is a real problem.
 	 *
 	 * @param reference
+	 * @param monitor
 	 * @return whether a problem
 	 */
-	protected boolean isProblem(IReference reference) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
 		// by default fragment -> host references are not problems
 		// https://bugs.eclipse.org/bugs/show_bug.cgi?id=255659
 		IApiMember member = reference.getResolvedReference();
@@ -553,6 +580,7 @@
 					return !lcomp.getHost().equals(member.getApiComponent());
 				}
 			} catch (CoreException ce) {
+				checkIfDisposed(reference.getMember().getApiComponent(), monitor);
 				ApiPlugin.log(ce);
 			}
 		}
@@ -1032,8 +1060,8 @@
 	 * @return the API problem if problem or null
 	 * @throws CoreException
 	 */
-	public IApiProblem checkAndCreateProblem(IReference reference) throws CoreException {
-		if (isProblem(reference) == false) {
+	public IApiProblem checkAndCreateProblem(IReference reference, IProgressMonitor monitor) throws CoreException {
+		if (isProblem(reference, monitor) == false) {
 			return null;
 		}
 		return createProblem(reference);
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractTypeLeakDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractTypeLeakDetector.java
index e3dbc9e..1bf3a20 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractTypeLeakDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/AbstractTypeLeakDetector.java
@@ -17,7 +17,7 @@
 import java.util.Set;
 
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.Flags;
 import org.eclipse.jdt.core.ISourceRange;
@@ -48,11 +48,11 @@
 	}
 
 	@Override
-	public boolean considerReference(IReference reference) {
+	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
 		// consider the reference if the location the reference is made from is
 		// visible:
 		// i.e. a public or protected class in an API package
-		if (super.considerReference(reference) && isNonAPIReference(reference)) {
+		if (super.considerReference(reference, monitor) && isNonAPIReference(reference)) {
 			IApiMember member = reference.getMember();
 			int modifiers = member.getModifiers();
 			if (((Flags.AccPublic | Flags.AccProtected) & modifiers) > 0) {
@@ -68,6 +68,7 @@
 					}
 				} catch (CoreException e) {
 					ApiPlugin.log(e.getStatus());
+					checkIfDisposed(reference.getMember().getApiComponent(), monitor);
 				}
 			}
 		}
@@ -75,7 +76,7 @@
 	}
 
 	@Override
-	protected boolean isProblem(IReference reference) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
 		IApiMember member = reference.getResolvedReference();
 		try {
 			IApiAnnotations annotations = member.getApiComponent().getApiDescription().resolveAnnotations(member.getHandle());
@@ -89,7 +90,7 @@
 					String memberName = member.getName();
 					if (memberName != null) {
 						if (!memberName.startsWith("javax.")) { //$NON-NLS-1$
-							ApiPlugin.log(new Status(IStatus.INFO, ApiPlugin.PLUGIN_ID, MessageFormat.format(BuilderMessages.AbstractTypeLeakDetector_vis_type_has_no_api_description, memberName)));
+							ApiPlugin.log(Status.info(MessageFormat.format(BuilderMessages.AbstractTypeLeakDetector_vis_type_has_no_api_description, memberName)));
 						}
 					}
 				} else {
@@ -99,6 +100,7 @@
 			}
 		} catch (CoreException e) {
 			ApiPlugin.log(e);
+			checkIfDisposed(member.getApiComponent(), monitor);
 		}
 		return false;
 	}
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 95b4511..e52943b 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
@@ -26,6 +26,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.jar.JarFile;
 
 import org.eclipse.core.resources.IFile;
@@ -33,9 +34,11 @@
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceStatus;
 import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.core.resources.IncrementalProjectBuilder;
 import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.resources.WorkspaceJob;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
@@ -44,6 +47,8 @@
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jdt.core.IClasspathAttribute;
 import org.eclipse.jdt.core.IClasspathEntry;
 import org.eclipse.jdt.core.ICompilationUnit;
@@ -178,6 +183,8 @@
 	 */
 	private BuildState buildstate = null;
 
+	private ConcurrentLinkedQueue<Runnable> markersQueue = new ConcurrentLinkedQueue<>();
+
 	/**
 	 * Bug 549838:  In case auto-building on a API tools settings change  is not desired,
 	 * specify VM property: {@code -Dorg.eclipse.disableAutoBuildOnSettingsChange=true}
@@ -190,6 +197,19 @@
 	 * @param resource
 	 */
 	void cleanupMarkers(IResource resource) {
+		if (isRunningAsJob()) {
+			new ApiAnalysisMarkersJob(() -> cleanupMarkersInternally(resource)).schedule();
+		} else {
+			cleanupMarkersInternally(resource);
+		}
+	}
+
+	/**
+	 * Cleans up markers associated with API Tools on the given resource.
+	 *
+	 * @param resource
+	 */
+	void cleanupMarkersInternally(IResource resource) {
 		cleanUnusedFilterMarkers(resource);
 		cleanupUsageMarkers(resource);
 		cleanupCompatibilityMarkers(resource);
@@ -342,6 +362,13 @@
 	}
 
 	@Override
+	public ISchedulingRule getRule(int kind, Map<String, String> args) {
+		// TODO probably we don't need even this and can return null if we are running as job
+		// if(isRunningAsJob()) return null;
+		return currentproject;
+	}
+
+	@Override
 	protected IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException {
 		PDEPreferencesManager prefs = PDECore.getDefault().getPreferencesManager();
 		boolean disableAPIAnalysisBuilder = prefs.getBoolean(ICoreConstants.DISABLE_API_ANALYSIS_BUILDER);
@@ -357,7 +384,6 @@
 		if (ApiPlugin.DEBUG_BUILDER) {
 			System.out.println("\nApiAnalysisBuilder: Starting build of " + this.currentproject.getName() + " @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$ //$NON-NLS-2$
 		}
-		SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.api_analysis_builder, 8);
 		IApiBaseline wbaseline = ApiPlugin.getDefault().getApiBaselineManager().getWorkspaceBaseline();
 		if (wbaseline == null) {
 			if (ApiPlugin.DEBUG_BUILDER) {
@@ -366,85 +392,93 @@
 			return NO_PROJECTS;
 		}
 		final IProject[] projects = getRequiredProjects(true);
+		if (kind != FULL_BUILD && kind != AUTO_BUILD && kind != INCREMENTAL_BUILD) {
+			return projects;
+		}
+		boolean fullBuild = kind == FULL_BUILD;
+		if (isRunningAsJob()) {
+			ApiAnalysisJob job = new ApiAnalysisJob(BuilderMessages.api_analysis_builder, currentproject, fullBuild,
+					wbaseline, projects);
+			job.cancelSimilarJobs(fullBuild);
+			job.schedule(100);
+			job.setPriority(Job.DECORATE);
+		} else {
+			work(fullBuild, wbaseline, projects, monitor);
+		}
+		return projects;
+	}
+
+	protected void work(final boolean fullBuild, IApiBaseline wbaseline, IProject[] projects, IProgressMonitor monitor)
+			throws CoreException {
+		SubMonitor localMonitor = SubMonitor.convert(monitor, BuilderMessages.api_analysis_builder, 8);
+
 		IApiBaseline baseline = ApiPlugin.getDefault().getApiBaselineManager().getDefaultApiBaseline();
 		try {
 			SubMonitor switchMonitor = localMonitor.split(4);
-			switch (kind) {
-				case FULL_BUILD: {
-					if (ApiPlugin.DEBUG_BUILDER) {
-						System.out.println("ApiAnalysisBuilder: Performing full build as requested"); //$NON-NLS-1$
-					}
-					buildAll(baseline, wbaseline, switchMonitor);
-					break;
+			if (fullBuild) {
+				if (ApiPlugin.DEBUG_BUILDER) {
+					System.out.println("ApiAnalysisBuilder: Performing full build as requested"); //$NON-NLS-1$
 				}
-				case AUTO_BUILD:
-				case INCREMENTAL_BUILD: {
-					this.buildstate = BuildState.getLastBuiltState(currentproject);
-					if (this.buildstate == null) {
+				buildAll(baseline, wbaseline, switchMonitor);
+			} else {
+				this.buildstate = BuildState.getLastBuiltState(currentproject);
+				if (this.buildstate == null) {
+					buildAll(baseline, wbaseline, switchMonitor);
+				} else if (worthDoingFullBuild(projects)) {
+					buildAll(baseline, wbaseline, switchMonitor);
+				} else {
+					IResourceDelta[] deltas = getDeltas(projects);
+					if (deltas.length < 1) {
 						buildAll(baseline, wbaseline, switchMonitor);
-						break;
-					} else if (worthDoingFullBuild(projects)) {
-						buildAll(baseline, wbaseline, switchMonitor);
-						break;
 					} else {
-						IResourceDelta[] deltas = getDeltas(projects);
-						if (deltas.length < 1) {
-							buildAll(baseline, wbaseline, switchMonitor);
-						} else {
-							IResourceDelta filters = null;
-							boolean full = false;
-							for (IResourceDelta delta : deltas) {
-								full = shouldFullBuild(delta);
-								if (full) {
-									break;
-								}
-								filters = delta.findMember(FILTER_PATH);
-								if (filters != null) {
-									switch (filters.getKind()) {
-										case IResourceDelta.ADDED:
-										case IResourceDelta.REMOVED: {
-											full = true;
-											break;
-										}
-										case IResourceDelta.CHANGED: {
-											full = (filters.getFlags() & (IResourceDelta.REPLACED | IResourceDelta.CONTENT)) > 0;
-											break;
-										}
-										default: {
-											break;
-										}
+						IResourceDelta filters = null;
+						boolean full = false;
+						for (IResourceDelta delta : deltas) {
+							full = shouldFullBuild(delta);
+							if (full) {
+								break;
+							}
+							filters = delta.findMember(FILTER_PATH);
+							if (filters != null) {
+								switch (filters.getKind()) {
+									case IResourceDelta.ADDED:
+									case IResourceDelta.REMOVED: {
+										full = true;
+										break;
 									}
-									if (full) {
+									case IResourceDelta.CHANGED: {
+										full = (filters.getFlags() & (IResourceDelta.REPLACED | IResourceDelta.CONTENT)) > 0;
+										break;
+									}
+									default: {
 										break;
 									}
 								}
-							}
-							if (full) {
-								if (ApiPlugin.DEBUG_BUILDER) {
-									System.out.println("ApiAnalysisBuilder: Performing full build since MANIFEST.MF or .api_filters was modified"); //$NON-NLS-1$
-								}
-								buildAll(baseline, wbaseline, switchMonitor);
-							} else {
-								switchMonitor.setWorkRemaining(2);
-								State state = (State) JavaModelManager.getJavaModelManager().getLastBuiltState(this.currentproject, switchMonitor.split(1));
-								if (state == null) {
-									buildAll(baseline, wbaseline, switchMonitor.split(1));
+								if (full) {
 									break;
 								}
+							}
+						}
+						if (full) {
+							if (ApiPlugin.DEBUG_BUILDER) {
+								System.out.println("ApiAnalysisBuilder: Performing full build since MANIFEST.MF or .api_filters was modified"); //$NON-NLS-1$
+							}
+							buildAll(baseline, wbaseline, switchMonitor);
+						} else {
+							switchMonitor.setWorkRemaining(2);
+							State state = (State) JavaModelManager.getJavaModelManager().getLastBuiltState(this.currentproject, switchMonitor.split(1));
+							if (state == null) {
+								buildAll(baseline, wbaseline, switchMonitor.split(1));
+							} else {
 								BuildState.setLastBuiltState(this.currentproject, null);
 								IncrementalApiBuilder builder = new IncrementalApiBuilder(this);
 								builder.build(baseline, wbaseline, deltas, state, this.buildstate, switchMonitor.split(1));
 							}
 						}
 					}
-					break;
-				}
-				default: {
-					break;
 				}
 			}
 			localMonitor.split(1);
-
 		} catch (OperationCanceledException oce) {
 			// do nothing, but don't forward it
 			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=304315
@@ -469,9 +503,10 @@
 					// be built do not close
 					// the baselines yet, they might be re-read by another build
 					// cycle
-					if (baseline != null) {
-						baseline.close();
-					}
+					// TODO: this seem to be not needed anymore.
+					//	if (baseline != null) {
+					//		baseline.close();
+					//	}
 				}
 				localMonitor.split(1);
 				if (this.buildstate != null) {
@@ -517,7 +552,79 @@
 		if (ApiPlugin.DEBUG_BUILDER) {
 			System.out.println("ApiAnalysisBuilder: Finished build of " + this.currentproject.getName() + " @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$ //$NON-NLS-2$
 		}
-		return projects;
+	}
+
+	public class ApiAnalysisJob extends Job {
+
+		private boolean fullBuild;
+		private IApiBaseline wbaseline;
+		private IProject[] projects;
+		private IProject project;
+
+		public ApiAnalysisJob(String name, IProject project, boolean fullBuild, IApiBaseline wbaseline,
+				IProject[] projects) {
+			super(name);
+			this.project = project;
+			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);
+		}
+
+		@Override
+		public IStatus run(IProgressMonitor monitor) {
+			try {
+				work(fullBuild, wbaseline, projects, monitor);
+			} catch (CoreException e) {
+				IStatus status = e.getStatus();
+				if (monitor.isCanceled()) {
+					return Status.CANCEL_STATUS;
+				} else {
+					if (status.getCode() == IResourceStatus.RESOURCE_NOT_FOUND && project.isAccessible()) {
+						waitForLockAndReschedule(monitor, e);
+						return Status.OK_STATUS;
+					} else {
+						return status;
+					}
+				}
+			}
+			return Status.OK_STATUS;
+		}
+
+		/**
+		 * In case the analysis job was interrupted by the build, let wait for the build
+		 * and start analysis again
+		 */
+		private void waitForLockAndReschedule(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) {
+				// nothing to do
+			} finally {
+				// release lock, we don't want to block workspace while analysis
+				Job.getJobManager().endRule(project);
+			}
+		}
+
+		@Override
+		public boolean belongsTo(Object family) {
+			return super.belongsTo(family) || ApiAnalysisJob.class == family;
+		}
+
+		void cancelSimilarJobs(boolean fullBuild) {
+			Job[] jobs = Job.getJobManager().find(ApiAnalysisJob.class);
+			for (Job job : jobs) {
+				ApiAnalysisJob ajob = (ApiAnalysisJob) job;
+				if (fullBuild == ajob.fullBuild && project.equals(ajob.project)) {
+					job.cancel();
+				}
+			}
+		}
 	}
 
 	/**
@@ -721,14 +828,30 @@
 			}
 		}
 
+		Runnable task;
 		if (hasFatalProblem) {
-			cleanupMarkers(project);
-			IApiProblem problem = ApiProblemFactory.newFatalProblem(Path.EMPTY.toString(), new String[] { project.getName() }, IApiProblem.FATAL_JDT_BUILDPATH_PROBLEM);
-			createMarkerForProblem(IApiProblem.CATEGORY_FATAL_PROBLEM, IApiMarkerConstants.FATAL_PROBLEM_MARKER, problem);
-			return true;
+			task = () -> {
+				cleanupMarkers(project);
+				IApiProblem problem = ApiProblemFactory.newFatalProblem(Path.EMPTY.toString(), new String[] { project.getName() }, IApiProblem.FATAL_JDT_BUILDPATH_PROBLEM);
+				createMarkerForProblem(IApiProblem.CATEGORY_FATAL_PROBLEM, IApiMarkerConstants.FATAL_PROBLEM_MARKER, problem);
+			};
+		} else {
+			task = () -> cleanupFatalMarkers(project);
 		}
-		cleanupFatalMarkers(project);
-		return false;
+
+		boolean runAsJob = isRunningAsJob();
+		if (runAsJob) {
+			new ApiAnalysisMarkersJob(task).schedule();
+		} else {
+			task.run();
+		}
+		return hasFatalProblem;
+	}
+
+	public static boolean isRunningAsJob() {
+		PDEPreferencesManager prefs = PDECore.getDefault().getPreferencesManager();
+		boolean runAsJob = prefs.getBoolean(ICoreConstants.RUN_API_ANALYSIS_AS_JOB);
+		return runAsJob;
 	}
 
 	/**
@@ -773,7 +896,7 @@
 			cleanupMarkers(this.currentproject);
 			IPluginModelBase currentModel = getCurrentModel();
 			if (currentModel != null) {
-				localMonitor.subTask(BuilderMessages.building_workspace_profile);
+				localMonitor.subTask(NLS.bind(BuilderMessages.building_workspace_profile, currentproject.getName()));
 				localMonitor.split(1);
 				String id = currentModel.getBundleDescription().getSymbolicName();
 				// Compatibility checks
@@ -810,11 +933,66 @@
 	}
 
 	/**
+	 * Creates or removes markers, uses the current project rule.
+	 * The tasks to do are maintained by markersQueue and executed in the submission order
+	 */
+	class ApiAnalysisMarkersJob extends WorkspaceJob {
+
+		public ApiAnalysisMarkersJob(Runnable task) {
+			super("Updating API analysis markers on " + currentproject.getName()); //$NON-NLS-1$
+			markersQueue.add(task);
+			setRule(currentproject);
+			setSystem(true);
+		}
+
+		@Override
+		public boolean belongsTo(Object family) {
+			return super.belongsTo(family) || ApiAnalysisMarkersJob.class == family;
+		}
+
+		@Override
+		public boolean shouldRun() {
+			return !markersQueue.isEmpty();
+		}
+
+		@Override
+		public boolean shouldSchedule() {
+			return !markersQueue.isEmpty();
+		}
+
+		@Override
+		public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
+			while (!markersQueue.isEmpty()) {
+				Runnable task = markersQueue.poll();
+				task.run();
+			}
+			return Status.OK_STATUS;
+		}
+
+	}
+
+	/**
 	 * Creates new markers are for the listing of problems added to this
 	 * reporter. If no problems have been added to this reporter, or we are not
 	 * running in the framework, no work is done.
 	 */
 	protected void createMarkers() {
+		IApiProblem[] problems = getAnalyzer().getProblems();
+		if (isRunningAsJob()) {
+			new ApiAnalysisMarkersJob(() -> createMarkersInternally(problems)).schedule();
+		} else {
+			createMarkersInternally(problems);
+		}
+	}
+
+	/**
+	 * Creates new markers are for the listing of problems added to this reporter.
+	 * If no problems have been added to this reporter, or we are not running in the
+	 * framework, no work is done.
+	 *
+	 * @param problems
+	 */
+	protected void createMarkersInternally(IApiProblem[] problems) {
 		try {
 			IResource manifest = Util.getManifestFile(this.currentproject);
 			if (manifest != null) {
@@ -825,7 +1003,6 @@
 		} catch (CoreException e) {
 			ApiPlugin.log(e);
 		}
-		IApiProblem[] problems = getAnalyzer().getProblems();
 		String type = null;
 		for (IApiProblem problem : problems) {
 			int category = problem.getCategory();
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/BaseApiAnalyzer.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/BaseApiAnalyzer.java
index 37abbf8..b7f7036 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/BaseApiAnalyzer.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/BaseApiAnalyzer.java
@@ -275,7 +275,7 @@
 					checkCompatibility(null, component, localMonitor.split(1));
 				}
 				// version checks
-				checkApiComponentVersion(reference, component);
+				checkApiComponentVersion(reference, component, baseline);
 				localMonitor.split(1);
 				checkfilters = true;
 			} else {
@@ -787,8 +787,43 @@
 	 * @param reference the given reference API component
 	 * @param component the given component
 	 */
-	private ReexportedBundleVersionInfo checkBundleVersionsOfReexportedBundles(IApiComponent reference, IApiComponent component) throws CoreException {
+	private ReexportedBundleVersionInfo checkAvailabilityAndBundleVersionsOfReexportedBundles(IApiComponent reference, IApiComponent component, IApiBaseline baseline) throws CoreException {
+		// check if the reexported bundles of reference is present as reexported
+		// in component, if not breaking change
+		Set<String> exportedRequiredComponentsInReference = new HashSet<>();
+		IRequiredComponentDescription[] requiredReference = reference.getRequiredComponents();
+		for (IRequiredComponentDescription element : requiredReference) {
+			if (element.isExported()) {
+				exportedRequiredComponentsInReference.add(element.getId());
+			}
+		}
 		IRequiredComponentDescription[] requiredComponents = component.getRequiredComponents();
+		for (IRequiredComponentDescription element : requiredComponents) {
+			if (element.isExported()) {
+				exportedRequiredComponentsInReference.remove(element.getId());
+			}
+		}
+
+		if (exportedRequiredComponentsInReference.size() > 0) {
+			String[] compNames = component.getPackageNames();
+			List<String> compNamesList = new ArrayList<>(Arrays.asList(compNames));
+			for (String comp : exportedRequiredComponentsInReference) {
+				IApiComponent baselineComponent = baseline.getApiComponent(comp);
+				if (baselineComponent == null) {
+					return new ReexportedBundleVersionInfo(exportedRequiredComponentsInReference.iterator().next(),
+							IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE);
+				}
+				String[] packNames = baselineComponent.getPackageNames();
+				List<String> packNamesList = new ArrayList<>(Arrays.asList(packNames));
+				// if the exported packages contains all exported package of
+				// dependency for which reexport was removed, we are good
+				if (!compNamesList.containsAll(packNamesList)) {
+					return new ReexportedBundleVersionInfo(exportedRequiredComponentsInReference.iterator().next(), IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE);
+				}
+			}
+		}
+
+		// check for the version ranges now
 		int length = requiredComponents.length;
 		ReexportedBundleVersionInfo info = null;
 		if (length != 0) {
@@ -2070,7 +2105,7 @@
 	 * @param reference
 	 * @param component
 	 */
-	private void checkApiComponentVersion(final IApiComponent reference, final IApiComponent component) throws CoreException {
+	private void checkApiComponentVersion(final IApiComponent reference, final IApiComponent component, final IApiBaseline baseline) throws CoreException {
 		if (reference == null || component == null) {
 			if (ApiPlugin.DEBUG_API_ANALYZER) {
 				System.out.println("Ignoring component version check"); //$NON-NLS-1$
@@ -2178,10 +2213,12 @@
 					case IApiProblem.MAJOR_VERSION_CHANGE_NO_BREAKAGE: {
 						// check if there is a version change required due to
 						// re-exported bundles
-						info = checkBundleVersionsOfReexportedBundles(reference, component);
+						info = checkAvailabilityAndBundleVersionsOfReexportedBundles(reference, component, baseline);
 						if (info != null) {
 							switch (info.kind) {
-								case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: {
+								case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE:
+								case IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE:
+								{
 									/*
 									 * we don't do anything since the major
 									 * version is already incremented we cancel
@@ -2210,7 +2247,7 @@
 					case IApiProblem.MINOR_VERSION_CHANGE: {
 						// check if there is a version change required due to
 						// re-exported bundles
-						info = checkBundleVersionsOfReexportedBundles(reference, component);
+						info = checkAvailabilityAndBundleVersionsOfReexportedBundles(reference, component, baseline);
 						if (info != null) {
 							switch (info.kind) {
 								case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: {
@@ -2220,6 +2257,14 @@
 											compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING);
 									break;
 								}
+								case IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE: {
+									// we keep this problem
+									newversion = new Version(compversion.getMajor() + 1, 0, 0, compversion.getQualifier() != null ? QUALIFIER : null);
+									problem = createVersionProblem(info.kind, new String[] {
+											compversionval,
+											info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING);
+									break;
+								}
 								default:
 									break;
 							}
@@ -2229,7 +2274,7 @@
 					case IApiProblem.MINOR_VERSION_CHANGE_NO_NEW_API: {
 						// check if there is a version change required due to
 						// re-exported bundles
-						info = checkBundleVersionsOfReexportedBundles(reference, component);
+						info = checkAvailabilityAndBundleVersionsOfReexportedBundles(reference, component, baseline);
 						if (info != null) {
 							switch (info.kind) {
 								case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: {
@@ -2239,6 +2284,13 @@
 											compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING);
 									break;
 								}
+								case IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE: {
+									// we return this one
+									newversion = new Version(compversion.getMajor() + 1, 0, 0, compversion.getQualifier() != null ? QUALIFIER : null);
+									problem = createVersionProblem(info.kind, new String[] {
+											compversionval, info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING);
+									break;
+								}
 								case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE: {
 									// we don't do anything since we already
 									// incremented the minor version
@@ -2256,7 +2308,7 @@
 						break;
 				}
 			} else {
-				info = checkBundleVersionsOfReexportedBundles(reference, component);
+				info = checkAvailabilityAndBundleVersionsOfReexportedBundles(reference, component, baseline);
 				if (info != null) {
 					switch (info.kind) {
 						case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE: {
@@ -2268,6 +2320,17 @@
 							}
 							break;
 						}
+						case IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE: {
+							// major version change
+							if (compversion.getMajor() <= refversion.getMajor()) {
+								newversion = new Version(compversion.getMajor() + 1, 0, 0,
+										compversion.getQualifier() != null ? QUALIFIER : null);
+								problem = createVersionProblem(info.kind, new String[] {
+										compversionval,
+										info.componentID, }, String.valueOf(newversion), Util.EMPTY_STRING);
+							}
+							break;
+						}
 						case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE: {
 							// minor version change if the component's major
 							// version is not greater than ref's major version
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/BuildState.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/BuildState.java
index 198130e..d774441 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/BuildState.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/BuildState.java
@@ -36,8 +36,6 @@
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.IClasspathEntry;
 import org.eclipse.jdt.core.IJavaProject;
@@ -568,7 +566,7 @@
 				}
 			} catch (Exception e) {
 				e.printStackTrace();
-				throw new CoreException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, Platform.PLUGIN_ERROR, "Error reading last build state for project " + project.getName(), e)); //$NON-NLS-1$
+				throw new CoreException(Status.error("Error reading last build state for project " + project.getName(), e)); //$NON-NLS-1$
 			}
 		} else if (ApiPlugin.DEBUG_BUILDER) {
 			if (file == null) {
@@ -650,7 +648,7 @@
 			} catch (SecurityException se) {
 				// could not delete file: cannot do much more
 			}
-			throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, Platform.PLUGIN_ERROR, NLS.bind(BuilderMessages.build_cannotSaveState, project.getName()), e));
+			throw new CoreException(Status.error(NLS.bind(BuilderMessages.build_cannotSaveState, project.getName()), e));
 		}
 		if (ApiPlugin.DEBUG_BUILDER) {
 			t = System.currentTimeMillis() - t;
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalAnnotationReferenceDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalAnnotationReferenceDetector.java
index f9cc96b..9c060df 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalAnnotationReferenceDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalAnnotationReferenceDetector.java
@@ -14,6 +14,7 @@
 package org.eclipse.pde.api.tools.internal.builder;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jdt.core.IAnnotatable;
 import org.eclipse.jdt.core.IAnnotation;
 import org.eclipse.jdt.core.ISourceRange;
@@ -59,8 +60,8 @@
 	}
 
 	@Override
-	public boolean considerReference(IReference reference) {
-		return super.considerReference(reference);
+	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
+		return super.considerReference(reference, monitor);
 	}
 
 	@Override
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalFieldReferenceDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalFieldReferenceDetector.java
index a8aae4c..0868fa9 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalFieldReferenceDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalFieldReferenceDetector.java
@@ -18,6 +18,7 @@
 import java.util.StringTokenizer;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jdt.core.IType;
 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.IDocument;
@@ -77,9 +78,9 @@
 	}
 
 	@Override
-	public boolean considerReference(IReference reference) {
+	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
 		MethodKey key = new MethodKey(reference.getReferencedTypeName(), reference.getReferencedMemberName(), reference.getReferencedSignature(), true);
-		if ((super.considerReference(reference) && fIllegalFields.containsKey(key)) || isEnclosedBy(reference.getReferencedTypeName(), fIllegalTypes.keySet())) {
+		if ((super.considerReference(reference, monitor) && fIllegalFields.containsKey(key)) || isEnclosedBy(reference.getReferencedTypeName(), fIllegalTypes.keySet())) {
 			retainReference(reference);
 			return true;
 		}
@@ -133,8 +134,8 @@
 	}
 
 	@Override
-	protected boolean isProblem(IReference reference) {
-		if (!super.isProblem(reference)) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
+		if (!super.isProblem(reference, monitor)) {
 			return false;
 		}
 		String componentId = fFieldComponents.get(reference.getResolvedReference().getHandle());
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalImplementsProblemDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalImplementsProblemDetector.java
index 07ef089..1edde8c 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalImplementsProblemDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalImplementsProblemDetector.java
@@ -16,6 +16,7 @@
 import java.util.HashMap;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
 import org.eclipse.pde.api.tools.internal.provisional.Factory;
 import org.eclipse.pde.api.tools.internal.provisional.IApiAnnotations;
@@ -57,9 +58,9 @@
 	}
 
 	@Override
-	public boolean considerReference(IReference reference) {
+	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
 		try {
-			if (super.considerReference(reference)) {
+			if (super.considerReference(reference, monitor)) {
 				return true;
 			}
 			IApiType type = (IApiType) reference.getMember();
@@ -79,15 +80,16 @@
 			if (ApiPlugin.DEBUG_PROBLEM_DETECTOR) {
 				ApiPlugin.log(ce);
 			}
+			checkIfDisposed(reference.getMember().getApiComponent(), monitor);
 		}
 		return false;
 	}
 
 	@Override
-	protected boolean isProblem(IReference reference) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
 		try {
 			if (isIllegalType(reference)) {
-				return super.isProblem(reference);
+				return super.isProblem(reference, monitor);
 			}
 			if (fRestrictedInterfaces.size() > 0) {
 				IApiMember member = reference.getMember();
@@ -101,8 +103,12 @@
 			if (ApiPlugin.DEBUG_PROBLEM_DETECTOR) {
 				ApiPlugin.log(ce);
 			}
+			IApiMember member = reference.getMember();
+			if (member != null) {
+				checkIfDisposed(member.getApiComponent(), monitor);
+			}
 		}
-		return super.isProblem(reference);
+		return super.isProblem(reference, monitor);
 	}
 
 	@Override
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalMethodReferenceDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalMethodReferenceDetector.java
index 502d7e9..d120b09 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalMethodReferenceDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/IllegalMethodReferenceDetector.java
@@ -18,6 +18,7 @@
 import java.util.StringTokenizer;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jdt.core.IType;
 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.IDocument;
@@ -51,8 +52,8 @@
 	}
 
 	@Override
-	public boolean considerReference(IReference reference) {
-		if (super.considerReference(reference)) {
+	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
+		if (super.considerReference(reference, monitor)) {
 			return true;
 		}
 		if (isEnclosedBy(reference.getReferencedTypeName(), fIllegalTypes.keySet())) {
@@ -63,8 +64,8 @@
 	}
 
 	@Override
-	protected boolean isProblem(IReference reference) {
-		if (super.isProblem(reference)) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
+		if (super.isProblem(reference, monitor)) {
 			return true;
 		}
 		// check the restricted types listing
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakExtendsProblemDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakExtendsProblemDetector.java
index 8522131..de1be2a 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakExtendsProblemDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakExtendsProblemDetector.java
@@ -19,6 +19,7 @@
 import java.util.Set;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jdt.core.Flags;
 import org.eclipse.pde.api.tools.internal.model.ApiType;
 import org.eclipse.pde.api.tools.internal.model.MethodKey;
@@ -68,8 +69,8 @@
 	}
 
 	@Override
-	public boolean isProblem(IReference reference) {
-		boolean isProb = super.isProblem(reference);
+	public boolean isProblem(IReference reference, IProgressMonitor monitor) {
+		boolean isProb = super.isProblem(reference, monitor);
 		problemFlags = IApiProblem.LEAK_EXTENDS;
 		//check if the no extend type is left to be extended
 		// or if noimplement interface is extended but not marked noimplement
@@ -108,6 +109,7 @@
 			}
 			catch (CoreException e) {
 				ApiPlugin.log(e);
+				checkIfDisposed(member.getApiComponent(), monitor);
 			}
 		}
 		if (isProb) {
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakFieldProblemDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakFieldProblemDetector.java
index bb31d2b..5f0a045 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakFieldProblemDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakFieldProblemDetector.java
@@ -16,6 +16,7 @@
 import java.util.Set;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jdt.core.Flags;
 import org.eclipse.jdt.core.IField;
 import org.eclipse.jdt.core.ISourceRange;
@@ -74,8 +75,8 @@
 	}
 
 	@Override
-	protected boolean isProblem(IReference reference) {
-		if (super.isProblem(reference)) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
+		if (super.isProblem(reference, monitor)) {
 			IApiField field = (IApiField) reference.getMember();
 			if ((Flags.AccProtected & field.getModifiers()) > 0) {
 				// TODO: could do this check before resolution - it's a check on
@@ -89,6 +90,7 @@
 					}
 				} catch (CoreException e) {
 					ApiPlugin.log(e);
+					checkIfDisposed(field.getApiComponent(), monitor);
 				}
 			}
 			return true;
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakImplementsProblemDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakImplementsProblemDetector.java
index fea946d..b30325c 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakImplementsProblemDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakImplementsProblemDetector.java
@@ -16,6 +16,7 @@
 import java.util.Set;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
 import org.eclipse.pde.api.tools.internal.provisional.IApiAnnotations;
 import org.eclipse.pde.api.tools.internal.provisional.RestrictionModifiers;
@@ -54,8 +55,8 @@
 	}
 
 	@Override
-	public boolean isProblem(IReference reference) {
-		boolean isProb = super.isProblem(reference);
+	public boolean isProblem(IReference reference, IProgressMonitor monitor) {
+		boolean isProb = super.isProblem(reference, monitor);
 		// check if no implement interface is implemented and thereby leaking api
 		// types from noimplement interface
 		if (isProb == false) {
@@ -74,6 +75,7 @@
 			}
 			catch (CoreException e) {
 				ApiPlugin.log(e);
+				checkIfDisposed(member.getApiComponent(), monitor);
 			}
 		}
 		return isProb;
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakReturnTypeDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakReturnTypeDetector.java
index a16b233..edb8f43 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakReturnTypeDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/LeakReturnTypeDetector.java
@@ -17,6 +17,7 @@
 import java.util.Set;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.jdt.core.Flags;
 import org.eclipse.pde.api.tools.internal.provisional.builder.IReference;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiType;
@@ -53,8 +54,8 @@
 	}
 
 	@Override
-	protected boolean isProblem(IReference reference) {
-		if (super.isProblem(reference) == true) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
+		if (super.isProblem(reference, monitor) == true) {
 			return true;
 		}
 		IApiType type = (IApiType) reference.getResolvedReference();
@@ -68,7 +69,8 @@
 							return true;
 						}
 					}
-				}catch (CoreException e) {
+				} catch (CoreException e) {
+					checkIfDisposed(reference.getMember().getApiComponent(), monitor);
 					// do nothing, skip it
 				}
 			}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/MethodLeakDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/MethodLeakDetector.java
index db130fe..43e5ad6 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/MethodLeakDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/MethodLeakDetector.java
@@ -17,7 +17,7 @@
 import java.util.Set;
 
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.Flags;
 import org.eclipse.jdt.core.IType;
@@ -70,7 +70,7 @@
 	}
 
 	@Override
-	protected boolean isProblem(IReference reference) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
 		IApiMethod method = (IApiMethod) reference.getMember();
 		IApiType type = (IApiType) reference.getResolvedReference();
 		try {
@@ -100,7 +100,7 @@
 					String typeName = type.getName();
 					if (typeName != null) {
 						if (!typeName.startsWith("javax.")) { //$NON-NLS-1$
-							ApiPlugin.log(new Status(IStatus.INFO, ApiPlugin.PLUGIN_ID, MessageFormat.format(BuilderMessages.AbstractTypeLeakDetector_vis_type_has_no_api_description, typeName)));
+							ApiPlugin.log(Status.info(MessageFormat.format(BuilderMessages.AbstractTypeLeakDetector_vis_type_has_no_api_description, typeName)));
 						}
 					}
 				} else {
@@ -110,6 +110,7 @@
 			}
 		} catch (CoreException e) {
 			ApiPlugin.log(e);
+			checkIfDisposed(method.getApiComponent(), monitor);
 		}
 		return false;
 	}
@@ -138,8 +139,8 @@
 	}
 
 	@Override
-	public boolean considerReference(IReference reference) {
-		if (super.considerReference(reference) && isNonAPIReference(reference)) {
+	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
+		if (super.considerReference(reference, monitor) && isNonAPIReference(reference)) {
 			IApiMember member = reference.getMember();
 			if (member != null && matchesSourceModifiers(member) && matchesSourceApiRestrictions(member)) {
 				retainReference(reference);
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceAnalyzer.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceAnalyzer.java
index 6890dbc..7db08bf 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceAnalyzer.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceAnalyzer.java
@@ -97,13 +97,19 @@
 					List<IReference> references = type.extractReferences(fAllReferenceKinds, null);
 					// keep potential matches
 					for (IReference ref : references) {
+						if (fMonitor.isCanceled()) {
+							break;
+						}
 						// compute index of interested problem detectors
 						int index = getLog2(ref.getReferenceKind());
 						IApiProblemDetector[] detectors = fIndexedDetectors[index];
 						boolean added = false;
 						if (detectors != null) {
 							for (IApiProblemDetector detector : detectors) {
-								if (detector.considerReference(ref)) {
+								if (fMonitor.isCanceled()) {
+									break;
+								}
+								if (detector.considerReference(ref, fMonitor)) {
 									if (!added) {
 										fReferences.add(ref);
 										added = true;
@@ -114,6 +120,7 @@
 					}
 				} catch (CoreException e) {
 					fStatus.add(e.getStatus());
+					AbstractProblemDetector.checkIfDisposed(classFile.getApiComponent(), fMonitor);
 				}
 			}
 		}
@@ -245,6 +252,9 @@
 			localMonitor.subTask(BuilderMessages.ReferenceAnalyzer_analyzing_api_checking_use);
 			SubMonitor loopMonitor = localMonitor.split(1).setWorkRemaining(detectors.length);
 			for (IApiProblemDetector detector : detectors) {
+				if (monitor.isCanceled()) {
+					break;
+				}
 				allProblems.addAll(detector.createProblems(loopMonitor.split(1)));
 			}
 			IApiProblem[] array = allProblems.toArray(new IApiProblem[allProblems.size()]);
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceExtractor.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceExtractor.java
index bc0acbc..244fc6b 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceExtractor.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/ReferenceExtractor.java
@@ -24,7 +24,6 @@
 import java.util.TreeSet;
 
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.pde.api.tools.internal.model.AbstractApiTypeRoot;
@@ -1277,8 +1276,8 @@
 			IApiType owner = (IApiType) this.getMember();
 			IApiField field = owner.getField(name);
 			if (field == null) {
-				ApiPlugin.log(new Status(IStatus.WARNING, ApiPlugin.PLUGIN_ID, NLS.bind(BuilderMessages.ReferenceExtractor_failed_to_lookup_field, new String[] {
-						name, Signatures.getQualifiedTypeSignature(owner) })));
+				ApiPlugin.log(Status.warning(NLS.bind(BuilderMessages.ReferenceExtractor_failed_to_lookup_field, name,
+						Signatures.getQualifiedTypeSignature(owner))));
 				// if we can't find the method there is no point trying to
 				// process it
 				return null;
@@ -1385,8 +1384,8 @@
 			}
 			IApiMethod method = owner.getMethod(name, desc);
 			if (method == null) {
-				ApiPlugin.log(new Status(IStatus.WARNING, ApiPlugin.PLUGIN_ID, NLS.bind(BuilderMessages.ReferenceExtractor_failed_to_lookup_method, new String[] {
-						name, desc, Signatures.getQualifiedTypeSignature(owner) })));
+				ApiPlugin.log(Status.warning(NLS.bind(BuilderMessages.ReferenceExtractor_failed_to_lookup_method,
+						new String[] { name, desc, Signatures.getQualifiedTypeSignature(owner) })));
 				// if we can't find the method there is no point trying to
 				// process it
 				return null;
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/SystemApiDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/SystemApiDetector.java
index 4f0b5cd..c783a43 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/SystemApiDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/SystemApiDetector.java
@@ -314,7 +314,7 @@
 	}
 
 	@Override
-	protected boolean isProblem(IReference reference) {
+	protected boolean isProblem(IReference reference, IProgressMonitor monitor) {
 		// the reference must be in the system library
 		try {
 			IApiMember member = reference.getMember();
@@ -361,12 +361,13 @@
 			}
 		} catch (CoreException e) {
 			ApiPlugin.log(e);
+			checkIfDisposed(reference.getMember().getApiComponent(), monitor);
 		}
 		return false;
 	}
 
 	@Override
-	public boolean considerReference(IReference reference) {
+	public boolean considerReference(IReference reference, IProgressMonitor monitor) {
 		try {
 			IApiComponent apiComponent = reference.getMember().getApiComponent();
 			IApiBaseline baseline = apiComponent.getBaseline();
@@ -393,6 +394,7 @@
 			}
 		} catch (CoreException e) {
 			ApiPlugin.log(e);
+			checkIfDisposed(reference.getMember().getApiComponent(), monitor);
 		}
 		return false;
 	}
@@ -408,8 +410,11 @@
 		List<IApiProblem> problems = new LinkedList<>();
 		SubMonitor loopMonitor = SubMonitor.convert(monitor, references.size());
 		for (IReference reference : references) {
+			if (monitor.isCanceled()) {
+				break;
+			}
 			loopMonitor.split(1);
-			if (isProblem(reference)) {
+			if (isProblem(reference, monitor)) {
 				try {
 					IApiProblem problem = null;
 					IApiComponent component = reference.getMember().getApiComponent();
@@ -425,6 +430,7 @@
 					}
 				} catch (CoreException e) {
 					ApiPlugin.log(e.getStatus());
+					AbstractProblemDetector.checkIfDisposed(reference.getMember().getApiComponent(), monitor);
 				}
 			}
 		}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/buildermessages.properties b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/buildermessages.properties
index bdb0c64..1a40006 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/buildermessages.properties
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/builder/buildermessages.properties
@@ -11,9 +11,9 @@
 # Contributors:
 #     IBM Corporation - initial API and implementation
 ###############################################################################
-api_analysis_builder=API Analysis Builder
+api_analysis_builder=Performing API Analysis
 api_analysis_on_0=Analyzing API
-building_workspace_profile=Building workspace API baseline
+building_workspace_profile=Building workspace API baseline for ''{0}''
 checking_api_usage=Checking API use of ''{0}''
 checking_external_dependencies=Checking external dependencies
 AbstractTypeLeakDetector_vis_type_has_no_api_description=Visible type {0} has no API description
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/comparator/ClassFileComparator.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/comparator/ClassFileComparator.java
index d18306c..bcd88a3 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/comparator/ClassFileComparator.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/comparator/ClassFileComparator.java
@@ -2485,7 +2485,7 @@
 			if (ApiPlugin.DEBUG_CLASSFILE_COMPARATOR) {
 				System.err.println("TYPE LOOKUP: " + msg); //$NON-NLS-1$
 			}
-			reportStatus(new Status(IStatus.ERROR, component.getSymbolicName(), msg));
+			reportStatus(Status.error(msg));
 			return null;
 		}
 		IApiTypeRoot result = Util.getClassFile(components, typeName);
@@ -2494,7 +2494,7 @@
 			if (ApiPlugin.DEBUG_CLASSFILE_COMPARATOR) {
 				System.err.println("TYPE LOOKUP: " + msg); //$NON-NLS-1$
 			}
-			reportStatus(new Status(IStatus.ERROR, component.getSymbolicName(), msg));
+			reportStatus(Status.error(msg));
 			return null;
 		}
 		return result;
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 3f1fc47..656cb6f 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2019 IBM Corporation and others.
+ * Copyright (c) 2007, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -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 HashMap<String, IApiComponent> fComponentsById = null;
+	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,11 @@
 	 * 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;
+
+	private volatile boolean restored;
 
 	/**
 	 * Constructs a new API baseline with the given name.
@@ -170,17 +177,18 @@
 	 */
 	public ApiBaseline(String name) {
 		super(null, IApiElement.BASELINE, name);
+		fComponentsProvidingPackageCache = new ConcurrentHashMap<>(8);
 		fAutoResolve = true;
-		fEEStatus = new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, CoreMessages.ApiBaseline_0);
+		fEEStatus = Status.error(CoreMessages.ApiBaseline_0);
 	}
 
 	/**
 	 * Constructs a new API baseline with the given attributes.
 	 *
-	 * @param name baseline name
+	 * @param name          baseline name
 	 * @param eeDescription execution environment description file
 	 * @throws CoreException if unable to create a baseline with the given
-	 *             attributes
+	 *                       attributes
 	 */
 	public ApiBaseline(String name, File eeDescription) throws CoreException {
 		this(name, eeDescription, null);
@@ -189,20 +197,19 @@
 	/**
 	 * Constructs a new API baseline with the given attributes.
 	 *
-	 * @param name baseline name
+	 * @param name          baseline name
 	 * @param eeDescription execution environment description file
-	 * @param location the given baseline location
+	 * @param location      the given baseline location
 	 * @throws CoreException if unable to create a baseline with the given
-	 *             attributes
+	 *                       attributes
 	 */
 	public ApiBaseline(String name, File eeDescription, String location) throws CoreException {
 		this(name);
 		if (eeDescription != null) {
 			fAutoResolve = false;
 			ExecutionEnvironmentDescription ee = new ExecutionEnvironmentDescription(eeDescription);
-			String profile = ee.getProperty(ExecutionEnvironmentDescription.CLASS_LIB_LEVEL);
 			initialize(ee);
-			fEEStatus = new Status(IStatus.OK, ApiPlugin.PLUGIN_ID, MessageFormat.format(CoreMessages.ApiBaseline_1, profile));
+			fEEStatus = Status.OK_STATUS;
 		}
 		this.fLocation = location;
 	}
@@ -297,7 +304,7 @@
 	/**
 	 * Initializes this baseline from the given properties.
 	 *
-	 * @param profile OGSi profile properties
+	 * @param profile     OGSi profile properties
 	 * @param description execution environment description
 	 * @throws CoreException if unable to initialize
 	 */
@@ -362,13 +369,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();
 	}
 
 	/**
@@ -377,7 +381,7 @@
 	 * @param component
 	 */
 	protected void addComponent(IApiComponent component) {
-		if (component == null) {
+		if (isDisposed() || component == null) {
 			return;
 		}
 		if (fComponentsById == null) {
@@ -398,7 +402,16 @@
 				}
 			} else {
 				TreeSet<IApiComponent> allComponents = new TreeSet<>(
-						(comp1, comp2) -> new Version(comp2.getVersion()).compareTo(new Version(comp1.getVersion())));
+						(comp1, comp2) -> {
+					if (comp2.getVersion().equals(comp1.getVersion())) {
+						if (comp2.getVersion().contains("JavaSE")) { //$NON-NLS-1$
+							ApiPlugin.logInfoMessage("Multiple locations for the same Java = " //$NON-NLS-1$
+									+ comp1.getLocation() + comp2.getLocation());
+						}
+						return 0;
+					}
+					return new Version(comp2.getVersion()).compareTo(new Version(comp1.getVersion()));
+				});
 				allComponents.add(comp);
 				allComponents.add(component);
 				fAllComponentsById.put(component.getSymbolicName(), allComponents);
@@ -417,6 +430,9 @@
 
 	@Override
 	public void addApiComponents(IApiComponent[] components) throws CoreException {
+		if (isDisposed()) {
+			return;
+		}
 		HashSet<String> ees = new HashSet<>();
 		for (IApiComponent apiComponent : components) {
 			BundleComponent component = (BundleComponent) apiComponent;
@@ -491,13 +507,13 @@
 							ExecutionEnvironmentDescription ee = new ExecutionEnvironmentDescription(file);
 							initialize(ee);
 						} catch (CoreException | IOException e) {
-							error = new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, CoreMessages.ApiBaseline_2, e);
+							error = Status.error(CoreMessages.ApiBaseline_2, e);
 						}
 					}
 				}
 			} else {
 				// no VMs match any required EE
-				error = new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, CoreMessages.ApiBaseline_6);
+				error = Status.error(CoreMessages.ApiBaseline_6);
 			}
 			if (error == null) {
 				// build status for unbound required EE's
@@ -508,11 +524,11 @@
 				}
 				missing.removeAll(covered);
 				if (missing.isEmpty()) {
-					fEEStatus = new Status(IStatus.OK, ApiPlugin.PLUGIN_ID, MessageFormat.format(CoreMessages.ApiBaseline_1, systemEE));
+					fEEStatus = Status.OK_STATUS;
 				} else {
 					MultiStatus multi = new MultiStatus(ApiPlugin.PLUGIN_ID, 0, CoreMessages.ApiBaseline_4, null);
 					for (String id : missing) {
-						multi.add(new Status(IStatus.WARNING, ApiPlugin.PLUGIN_ID, MessageFormat.format(CoreMessages.ApiBaseline_5, id)));
+						multi.add(Status.warning(MessageFormat.format(CoreMessages.ApiBaseline_5, id)));
 					}
 					fEEStatus = multi;
 				}
@@ -537,30 +553,30 @@
 	@Override
 	public IApiComponent[] getApiComponents() {
 		loadBaselineInfos();
-		if (fComponentsById == null) {
+		return getAlreadyLoadedApiComponents();
+	}
+
+	protected IApiComponent[] getAlreadyLoadedApiComponents() {
+		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
@@ -605,7 +621,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) {
@@ -696,8 +713,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;
 	}
@@ -705,22 +727,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
@@ -733,17 +758,52 @@
 	 * is accessed
 	 */
 	private void loadBaselineInfos() {
-		if (fComponentsById != null) {
+		if (disposed || restored) {
+			return;
+		}
+		ApiBaselineManager manager = ApiBaselineManager.getManager();
+		if (fComponentsById != null && manager.isBaselineLoaded(this)) {
+			return;
+		}
+		if (disposed || restored) {
 			return;
 		}
 		try {
-			ApiBaselineManager.getManager().loadBaselineInfos(this);
+			manager.loadBaselineInfos(this);
 		} catch (CoreException ce) {
 			ApiPlugin.log(ce);
 		}
 	}
 
 	/**
+	 * Restore a baseline from the given input stream (persisted baseline).
+	 *
+	 * @param stream the given input stream, will be closed by caller
+	 * @throws CoreException if unable to restore the baseline
+	 */
+	public void restoreFrom(InputStream stream) throws CoreException {
+		if (disposed || restored) {
+			return;
+		}
+		IApiComponent[] components = ApiBaselineManager.getManager().readBaselineComponents(this, stream);
+		if (components == null) {
+			restored = true;
+			return;
+		}
+		synchronized (this) {
+			if (disposed || restored) {
+				for (IApiComponent component : components) {
+					component.dispose();
+				}
+				return;
+			}
+			this.addApiComponents(components);
+			restored = true;
+		}
+	}
+
+
+	/**
 	 * Returns all errors in the state.
 	 *
 	 * @return state errors
@@ -798,15 +858,27 @@
 		fState = null;
 	}
 
+	@Override
+	public boolean isDisposed() {
+		return disposed;
+	}
+
 	/**
 	 * performs the actual dispose of mappings and cached elements
 	 */
 	protected void doDispose() {
+		if (disposed) {
+			return;
+		}
+		IApiComponent[] components;
+		synchronized (this) {
+			components = getAlreadyLoadedApiComponents();
+			disposed = true;
+		}
+		clearCachedElements();
 		if (ApiPlugin.isRunningInFramework()) {
 			JavaRuntime.removeVMInstallChangedListener(this);
 		}
-		clearCachedElements();
-		IApiComponent[] components = getApiComponents();
 		for (IApiComponent component2 : components) {
 			component2.dispose();
 		}
@@ -904,10 +976,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
@@ -988,9 +1058,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/ApiModelCache.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiModelCache.java
index 9a0f582..64f9d64 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiModelCache.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiModelCache.java
@@ -13,11 +13,11 @@
  *******************************************************************************/
 package org.eclipse.pde.api.tools.internal.model;
 
-import java.util.Enumeration;
+import java.util.List;
 
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.jdt.internal.core.OverflowingLRUCache;
 import org.eclipse.jdt.internal.core.util.LRUCache;
+import org.eclipse.pde.api.tools.internal.SynchronizedOverflowingLRUCache;
 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
@@ -33,7 +33,7 @@
 	/**
 	 * Cache used for {@link IApiElement}s
 	 */
-	static class Cache<K, V> extends OverflowingLRUCache<K, V> {
+	static class Cache<K, V> extends SynchronizedOverflowingLRUCache<K, V> {
 
 		/**
 		 * Constructor
@@ -55,14 +55,6 @@
 			return new Cache<>(size, newOverflow);
 		}
 
-		/**
-		 * Returns if the cache has any elements in it or not
-		 *
-		 * @return true if the cache has no entries, false otherwise
-		 */
-		public boolean isEmpty() {
-			return !keys().hasMoreElements();
-		}
 	}
 
 	static final int DEFAULT_CACHE_SIZE = 1000;
@@ -261,9 +253,8 @@
 	}
 
 	private IApiElement getElementInfoFromAnyBaseline(String baselineid, String componentid, String updatedIdentifier) {
-			Enumeration<String> elements = fRootCache.keys();
-			while (elements.hasMoreElements()) {
-				String otherBaselines = elements.nextElement();
+		List<String> elements = fRootCache.keysSnapshot();
+		for (String otherBaselines : elements) {
 				if (otherBaselines.equals(baselineid)) {
 					continue;
 				}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiType.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiType.java
index f8cbea8..88c648e 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiType.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/ApiType.java
@@ -307,9 +307,8 @@
 		return structure;
 	}
 
-	private Status createUnresolvedSuperClassStatus(String qName) {
-		return new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, ApiPlugin.REPORT_RESOLUTION_ERRORS,
-				MessageFormat.format(Messages.ApiType_1, qName, getName()), null);
+	private IStatus createUnresolvedSuperClassStatus(String qName) {
+		return Status.error(MessageFormat.format(Messages.ApiType_1, qName, getName()), null);
 	}
 
 	private void reOrganizeComponents(IApiComponent[] components) throws CoreException {
@@ -365,7 +364,7 @@
 	 * @throws CoreException
 	 */
 	private void requiresApiComponent() throws CoreException {
-		throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, Messages.ApiType_2));
+		throw new CoreException(Status.error(Messages.ApiType_2));
 	}
 
 	@Override
@@ -549,7 +548,8 @@
 				qName.append(simpleName);
 				file = getApiComponent().findTypeRoot(qName.toString());
 				if (file == null) {
-					throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, MessageFormat.format(Messages.ApiType_3, simpleName, getName())));
+					throw new CoreException(
+							Status.error(MessageFormat.format(Messages.ApiType_3, simpleName, getName())));
 				}
 				fMemberTypes.put(simpleName, file);
 			}
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 af4f09a..20078a1 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,33 @@
 	/**
 	 * 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;
 
 	/**
 	 * Constructs a new API component from the specified location in the file
@@ -178,6 +178,9 @@
 
 	@Override
 	public void dispose() {
+		if (isDisposed()) {
+			return;
+		}
 		try {
 			super.dispose();
 		} finally {
@@ -195,28 +198,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 +280,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(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, "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(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, "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 +359,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
@@ -386,7 +403,8 @@
 			if (component != null) {
 				descriptions.add(component.getApiDescription());
 			} else {
-				ApiPlugin.log(new Status(IStatus.WARNING, ApiPlugin.PLUGIN_ID, NLS.bind(Messages.BundleComponent_failed_to_lookup_fragment, fragments[i].getSymbolicName())));
+				ApiPlugin.log(Status.warning(
+						NLS.bind(Messages.BundleComponent_failed_to_lookup_fragment, fragments[i].getSymbolicName())));
 			}
 		}
 		return new CompositeApiDescription(descriptions.toArray(new IApiDescription[descriptions.size()]));
@@ -541,7 +559,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
@@ -628,7 +646,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) {
@@ -733,7 +751,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;
@@ -745,7 +763,7 @@
 				file = new File(parent, entry.getName());
 				String destCanonicalPath = file.getCanonicalPath();
 				if (!destCanonicalPath.startsWith(parentDirCanonicalPath + File.separator)) {
-					throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, MessageFormat.format("Entry is outside of the target dir: : {0}", entry.getName()), null)); //$NON-NLS-1$
+					throw new CoreException(Status.error(MessageFormat.format("Entry is outside of the target dir: : {0}", entry.getName()))); //$NON-NLS-1$
 				}
 				if (entry.isDirectory()) {
 					file.mkdir();
@@ -767,7 +785,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;
@@ -805,7 +823,7 @@
 		return file;
 	}
 
-	public void closingZipFileAndStream(InputStream stream, ZipFile jarFile) {
+	public static void closingZipFileAndStream(InputStream stream, ZipFile jarFile) {
 		try {
 			if (stream != null) {
 				stream.close();
@@ -830,7 +848,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 {
@@ -867,7 +885,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;
@@ -907,7 +925,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$
@@ -925,11 +943,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
@@ -939,11 +954,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];
@@ -953,7 +965,7 @@
 	}
 
 	@Override
-	public synchronized String getVersion() {
+	public String getVersion() {
 		init();
 		// remove the qualifier
 		StringBuilder buffer = new StringBuilder();
@@ -970,14 +982,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
@@ -994,16 +1008,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
@@ -1017,11 +1035,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));
@@ -1033,7 +1055,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;
@@ -1041,7 +1063,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;
@@ -1057,7 +1079,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();
@@ -1088,21 +1110,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());
 		}
@@ -1110,7 +1124,7 @@
 	}
 
 	@Override
-	public synchronized boolean hasFragments() throws CoreException {
+	public boolean hasFragments() throws CoreException {
 		return getBundleDescription().getFragments().length != 0;
 	}
 
@@ -1140,8 +1154,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:
@@ -1223,19 +1246,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;
 			}
@@ -1249,6 +1268,8 @@
 	 * @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));
 	}
+
 }
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..0a63a65 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,19 @@
 	/**
 	 * 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;
+
+	private volatile boolean disposed;
 
 	/**
 	 * Constructs an API component in the given {@link IApiBaseline}.
@@ -85,6 +87,7 @@
 		} finally {
 			synchronized (this) {
 				fApiDescription = null;
+				disposed = true;
 			}
 		}
 	}
@@ -95,8 +98,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 +113,7 @@
 	 *
 	 * @return whether this component has created an API description
 	 */
-	protected synchronized boolean isApiDescriptionInitialized() {
+	protected boolean isApiDescriptionInitialized() {
 		return fApiDescription != null;
 	}
 
@@ -116,17 +122,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 +150,11 @@
 	@Override
 	public IApiFilterStore getFilterStore() throws CoreException {
 		if (fFilterStore == null) {
-			fFilterStore = createApiFilterStore();
+			synchronized (this) {
+				if (fFilterStore == null) {
+					fFilterStore = createApiFilterStore();
+				}
+			}
 		}
 		return fFilterStore;
 	}
@@ -170,8 +180,17 @@
 	@Override
 	public IReferenceCollection getExternalDependencies() {
 		if (fReferences == null) {
-			fReferences = new UseScanReferences();
+			synchronized (this) {
+				if (fReferences == null) {
+					fReferences = new UseScanReferences();
+				}
+			}
 		}
 		return fReferences;
 	}
+
+	@Override
+	public boolean isDisposed() {
+		return disposed;
+	}
 }
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/TypeStructureBuilder.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/TypeStructureBuilder.java
index 4bfe41b..b492b6c 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/TypeStructureBuilder.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/model/TypeStructureBuilder.java
@@ -21,7 +21,6 @@
 import java.util.Map;
 
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
 import org.eclipse.osgi.util.NLS;
@@ -174,8 +173,7 @@
 
 	private static IApiType logAndReturn(IApiTypeRoot file, Exception e) {
 		if (ApiPlugin.DEBUG_BUILDER) {
-			IStatus status = new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, NLS.bind(Messages.TypeStructureBuilder_badClassFileEncountered, file.getTypeName()), e);
-			ApiPlugin.log(status);
+			ApiPlugin.log(Status.error(NLS.bind(Messages.TypeStructureBuilder_badClassFileEncountered, file.getTypeName()), e));
 		}
 		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..d7c1642 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,16 +69,23 @@
 
 	// 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
 	public void addApiComponents(IApiComponent[] components) throws CoreException {
+		if (isDisposed()) {
+			return;
+		}
 		HashSet<String> ees = new HashSet<>();
 		for (IApiComponent apiComponent : components) {
 			BundleComponent component = (BundleComponent) apiComponent;
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/ApiProblem.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/ApiProblem.java
index 963c119..767399c 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/ApiProblem.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/ApiProblem.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2008, 2020 IBM Corporation and others.
+ * Copyright (c) 2008, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -411,6 +411,8 @@
 				return "MINOR_VERSION_CHANGE_NO_NEW_API"; //$NON-NLS-1$
 			case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE:
 				return "REEXPORTED_MAJOR_VERSION_CHANGE"; //$NON-NLS-1$
+			case IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE:
+				return "REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE"; //$NON-NLS-1$
 			case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE:
 				return "REEXPORTED_MINOR_VERSION_CHANGE"; //$NON-NLS-1$
 			default:
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/ApiProblemFactory.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/ApiProblemFactory.java
index 51430c1..07f381d 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/ApiProblemFactory.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/ApiProblemFactory.java
@@ -618,6 +618,8 @@
 						return 56;
 					case IApiProblem.REEXPORTED_MAJOR_VERSION_CHANGE:
 						return 19;
+					case IApiProblem.REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE:
+						return 62;
 					case IApiProblem.REEXPORTED_MINOR_VERSION_CHANGE:
 						return 20;
 					case IApiProblem.MINOR_VERSION_CHANGE_EXECUTION_ENV_CHANGED:
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/problemmessages.properties b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/problemmessages.properties
index 035eea3..5a26ae3 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/problemmessages.properties
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/problems/problemmessages.properties
@@ -11,7 +11,7 @@
 # Contributors:
 #     IBM Corporation - initial API and implementation
 ###############################################################################
-# available message ids 62, 63, 65, 68, 70, 71, 75, 80,
+# available message ids  63, 65, 68, 70, 71, 75, 80,
 # 82, 83, 88, 90, 93
 
 #API baseline 
@@ -36,6 +36,7 @@
 56 = The minor version should be the same for version {0}, since no new APIs have been added since version {1}
 19 = The major version should be incremented in version {0}, because the modification of the version range for the re-exported bundle {1} requires a major version change
 20 = The minor version should be incremented in version {0}, because the modification of the version range for the re-exported bundle {1} requires a minor version change
+62 = The major version should be incremented in version {0}, because the bundle {1} is no longer re-exported
 
 #API usage problems
 #{0} = referenced type name
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/ApiPlugin.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/ApiPlugin.java
index fccd9da..8c3f538 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/ApiPlugin.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/ApiPlugin.java
@@ -445,7 +445,7 @@
 	 * @param t throwable to log
 	 */
 	public static void log(String message, Throwable t) {
-		log(newErrorStatus(message, t));
+		log(Status.error(message, t));
 	}
 
 	/**
@@ -477,23 +477,7 @@
 		// this message is intentionally not internationalized, as an exception
 		// may
 		// be due to the resource bundle itself
-		log(newInfoStatus("Internal message logged from API Tools Core: " + message, null)); //$NON-NLS-1$
-	}
-
-	/**
-	 * Returns a new error status for this plug-in with the given message
-	 *
-	 * @param message the message to be included in the status
-	 * @param exception the exception to be included in the status or
-	 *            <code>null</code> if none
-	 * @return a new error status
-	 */
-	public static IStatus newErrorStatus(String message, Throwable exception) {
-		return new Status(IStatus.ERROR, PLUGIN_ID, INTERNAL_ERROR, message, exception);
-	}
-
-	private static IStatus newInfoStatus(String message, Throwable exception) {
-		return new Status(IStatus.INFO, PLUGIN_ID, INTERNAL_ERROR, message, exception);
+		log(Status.info("Internal message logged from API Tools Core: " + message)); //$NON-NLS-1$
 	}
 
 	/**
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/Factory.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/Factory.java
index 343742b..38177cd 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/Factory.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/Factory.java
@@ -18,7 +18,6 @@
 import java.util.LinkedList;
 
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.api.tools.internal.builder.TypeScope;
 import org.eclipse.pde.api.tools.internal.descriptors.ComponentDescriptorImpl;
@@ -214,7 +213,7 @@
 					}
 				}
 			}
-			throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, MessageFormat.format("Unable to resolve method signature: {0}", descriptor.toString()), null)); //$NON-NLS-1$
+			throw new CoreException(Status.error(MessageFormat.format("Unable to resolve method signature: {0}", descriptor ), null)); //$NON-NLS-1$
 		}
 		return descriptor;
 	}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/builder/IApiProblemDetector.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/builder/IApiProblemDetector.java
index 62f0da8..d9a69e0 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/builder/IApiProblemDetector.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/builder/IApiProblemDetector.java
@@ -38,9 +38,10 @@
 	 * for further analysis once references have been resolved.
 	 *
 	 * @param reference potential problem
+	 * @param monitor the monitor to report progress.
 	 * @return whether the unresolved reference is a potential problem
 	 */
-	public boolean considerReference(IReference reference);
+	public boolean considerReference(IReference reference, IProgressMonitor monitor);
 
 	/**
 	 * Returns a list of any problems detected after analyzing potential
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/ApiComparator.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/ApiComparator.java
index a88d015..e92148f 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/ApiComparator.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/ApiComparator.java
@@ -21,6 +21,7 @@
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.jdt.core.Flags;
+import org.eclipse.pde.api.tools.internal.builder.AbstractProblemDetector;
 import org.eclipse.pde.api.tools.internal.comparator.ClassFileComparator;
 import org.eclipse.pde.api.tools.internal.comparator.Delta;
 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
@@ -786,11 +787,13 @@
 								}
 							} catch (CoreException e) {
 								ApiPlugin.log(e);
+								AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), iterationMonitor);
 							}
 						}
 					});
 				} catch (CoreException e) {
 					ApiPlugin.log(e);
+					AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), loopMonitor);
 				}
 			}
 		}
@@ -908,11 +911,13 @@
 											}
 										} catch (CoreException e) {
 											ApiPlugin.log(e);
+											AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), apiContainerIterationMonitor);
 										}
 									}
 								});
 							} catch (CoreException e) {
 								ApiPlugin.log(e);
+								AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), apiContainerIterationMonitor);
 							}
 						}
 					}
@@ -952,11 +957,13 @@
 										Util.getComponentVersionsId(component2) }));
 							} catch (CoreException e) {
 								ApiPlugin.log(e);
+								AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), iterationMonitor);
 							}
 						}
 					});
 				} catch (CoreException e) {
 					ApiPlugin.log(e);
+					AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), iterationMonitor);
 				}
 			}
 		}
@@ -1008,11 +1015,13 @@
 													Util.getComponentVersionsId(component) }));
 										} catch (CoreException e) {
 											ApiPlugin.log(e);
+											AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), typeContainerIterationMonitor);
 										}
 									}
 								});
 							} catch (CoreException e) {
 								ApiPlugin.log(e);
+								AbstractProblemDetector.checkIfDisposed(container.getApiComponent(), typeContainerIterationMonitor);
 							}
 						}
 					}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/ApiScope.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/ApiScope.java
index 7bebdae..6589aff 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/ApiScope.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/ApiScope.java
@@ -16,11 +16,9 @@
 import java.util.ArrayList;
 
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.pde.api.tools.internal.model.Messages;
-import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
 import org.eclipse.pde.api.tools.internal.provisional.model.ApiScopeVisitor;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
@@ -72,7 +70,7 @@
 					break;
 				}
 				default:
-					throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, NLS.bind(Messages.ApiScope_0, Util.getApiElementType(type))));
+					throw new CoreException(Status.error(NLS.bind(Messages.ApiScope_0, Util.getApiElementType(type))));
 			}
 		}
 	}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/CompareApiScopeVisitor.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/CompareApiScopeVisitor.java
index 0610a2a..348d3e9 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/CompareApiScopeVisitor.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/comparator/CompareApiScopeVisitor.java
@@ -17,6 +17,7 @@
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.pde.api.tools.internal.builder.AbstractProblemDetector;
 import org.eclipse.pde.api.tools.internal.comparator.Delta;
 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
 import org.eclipse.pde.api.tools.internal.provisional.model.ApiScopeVisitor;
@@ -79,6 +80,7 @@
 					compareApiTypeRoot(typeroot);
 				} catch (CoreException e) {
 					ApiPlugin.log(e);
+					AbstractProblemDetector.checkIfDisposed(typeroot.getApiComponent(), localMonitor);
 				}
 			}
 		});
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/model/IApiBaseline.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/model/IApiBaseline.java
index ba5a7b1..59880a1 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/model/IApiBaseline.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/model/IApiBaseline.java
@@ -167,4 +167,9 @@
 	 * @param location the new location of the baseline
 	 */
 	public void setLocation(String location);
+
+	/**
+	 * @return true if the current baseline is disposed
+	 */
+	public boolean isDisposed();
 }
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/model/IApiComponent.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/model/IApiComponent.java
index fb322cd..cfd2d4e 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/model/IApiComponent.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/model/IApiComponent.java
@@ -242,4 +242,9 @@
 	 * @return the collection of reference descriptors
 	 */
 	public IReferenceCollection getExternalDependencies();
+
+	/**
+	 * @return true if the current component is disposed
+	 */
+	public boolean isDisposed();
 }
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/problems/IApiProblem.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/problems/IApiProblem.java
index 6ccafb7..8410191 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/problems/IApiProblem.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/problems/IApiProblem.java
@@ -223,6 +223,17 @@
 	public static final int MINOR_VERSION_CHANGE_UNNECESSARILY = 9;
 
 	/**
+	 * Constant representing the value of the major version change
+	 * {@link IApiProblem} kind as a consequence of a major version change in a
+	 * re-exported being removed. <br>
+	 * Value is: <code>10</code>
+	 *
+	 * @see #getKind()
+	 * @see #CATEGORY_VERSION
+	 */
+	public static final int REEXPORTED_REMOVAL_OF_REEXPORT_MAJOR_VERSION_CHANGE = 10;
+
+	/**
 	 * Constant representing the value of an illegal extend {@link IApiProblem}
 	 * kind. <br>
 	 * Value is: <code>1</code>
@@ -230,6 +241,7 @@
 	 * @see #getKind()
 	 * @see #CATEGORY_USAGE
 	 */
+
 	public static final int ILLEGAL_EXTEND = 1;
 
 	/**
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/scanner/TagScanner.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/scanner/TagScanner.java
index 049516c..412bca4 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/scanner/TagScanner.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/scanner/TagScanner.java
@@ -22,7 +22,6 @@
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.jdt.core.Flags;
@@ -672,12 +671,12 @@
 			inputStream = source.getInputStream();
 			parser.setSource(Util.getInputStreamAsCharArray(inputStream, source.getEncoding()));
 		} catch (FileNotFoundException e) {
-			throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, MessageFormat.format("Compilation unit source not found: {0}", source.getName()), e)); //$NON-NLS-1$
+			throw new CoreException(Status.error(MessageFormat.format("Compilation unit source not found: {0}", source.getName()), e)); //$NON-NLS-1$
 		} catch (IOException e) {
 			if (ApiPlugin.DEBUG_TAG_SCANNER) {
 				System.err.println(source.getName());
 			}
-			throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, MessageFormat.format("Error reading compilation unit: {0}", source.getName()), e)); //$NON-NLS-1$
+			throw new CoreException(Status.error(MessageFormat.format("Error reading compilation unit: {0}", source.getName()), e)); //$NON-NLS-1$
 		} finally {
 			if (inputStream != null) {
 				try {
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/search/ApiSearchEngine.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/search/ApiSearchEngine.java
index e5932fb..0109709 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/search/ApiSearchEngine.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/search/ApiSearchEngine.java
@@ -157,7 +157,7 @@
 		IReference ref = null;
 		SubMonitor localmonitor = SubMonitor.convert(monitor, references.size());
 		IApiMember member = null;
-		for (Iterator<IReference> iter = references.iterator(); iter.hasNext();) {
+		for (Iterator<IReference> iter = references.iterator(); iter.hasNext() && !monitor.isCanceled();) {
 			if (localmonitor.isCanceled()) {
 				return Collections.emptyList();
 			}
@@ -167,7 +167,7 @@
 				continue;
 			}
 			localmonitor.setTaskName(MessageFormat.format(SearchMessages.ApiSearchEngine_searching_for_use_from, fRequestorContext, type.getName()));
-			if (requestor.acceptReference(ref)) {
+			if (requestor.acceptReference(ref, monitor)) {
 				refs.add(ref);
 			}
 			localmonitor.worked(1);
@@ -287,7 +287,7 @@
 					if (mstatus == null) {
 						mstatus = new MultiStatus(ApiPlugin.PLUGIN_ID, IStatus.ERROR, null, null);
 					}
-					mstatus.add(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, ce.getMessage(), ce));
+					mstatus.add(Status.error(ce.getMessage(), ce));
 				}
 			}
 			if (ApiPlugin.DEBUG_SEARCH_ENGINE) {
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/search/IApiSearchRequestor.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/search/IApiSearchRequestor.java
index 180eaa5..f6fe357 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/search/IApiSearchRequestor.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/provisional/search/IApiSearchRequestor.java
@@ -13,6 +13,7 @@
  *******************************************************************************/
 package org.eclipse.pde.api.tools.internal.provisional.search;
 
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.pde.api.tools.internal.provisional.builder.IReference;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember;
@@ -94,7 +95,7 @@
 	 * @param reference
 	 * @return true if the reference should be accepted, false otherwise
 	 */
-	public boolean acceptReference(IReference reference);
+	public boolean acceptReference(IReference reference, IProgressMonitor monitor);
 
 	/**
 	 * Returns the or'd listing of {@link IReference} kinds to look for.
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/search/UseScanManager.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/search/UseScanManager.java
index cd18bf3..1b774b8 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/search/UseScanManager.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/search/UseScanManager.java
@@ -18,6 +18,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.List;
 import java.util.jar.JarFile;
 import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
@@ -31,9 +32,9 @@
 import org.eclipse.core.runtime.preferences.InstanceScope;
 import org.eclipse.core.variables.IStringVariableManager;
 import org.eclipse.core.variables.VariablesPlugin;
-import org.eclipse.jdt.internal.core.OverflowingLRUCache;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.pde.api.tools.internal.IApiCoreConstants;
+import org.eclipse.pde.api.tools.internal.SynchronizedOverflowingLRUCache;
 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
 import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
 import org.eclipse.pde.api.tools.internal.util.FileManager;
@@ -63,7 +64,7 @@
 	 * Cache to maintain the list of least recently used
 	 * <code>UseScanReferences</code>
 	 */
-	private static class UseScanCache extends OverflowingLRUCache<IApiComponent, IReferenceCollection> {
+	private static class UseScanCache extends SynchronizedOverflowingLRUCache<IApiComponent, IReferenceCollection> {
 
 		public UseScanCache(int size) {
 			super(size);
@@ -407,9 +408,8 @@
 	 * Purges all reference information
 	 */
 	public void clearCache() {
-		Enumeration<IReferenceCollection> elements = fApiComponentCache.elements();
-		while (elements.hasMoreElements()) {
-			IReferenceCollection reference = elements.nextElement();
+		List<IReferenceCollection> elements = fApiComponentCache.elementsSnapshot();
+		for (IReferenceCollection reference : elements) {
 			reference.clear();
 		}
 		fApiComponentCache.flush();
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/search/UseSearchRequestor.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/search/UseSearchRequestor.java
index 122a21c..3a631f5 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/search/UseSearchRequestor.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/search/UseSearchRequestor.java
@@ -16,6 +16,7 @@
 import java.util.Set;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.pde.api.tools.internal.AntFilterStore;
 import org.eclipse.pde.api.tools.internal.builder.AbstractProblemDetector;
 import org.eclipse.pde.api.tools.internal.builder.ProblemDetectorBuilder;
@@ -203,7 +204,7 @@
 	}
 
 	@Override
-	public boolean acceptReference(IReference reference) {
+	public boolean acceptReference(IReference reference, IProgressMonitor monitor) {
 		try {
 			IApiMember member = reference.getResolvedReference();
 			if (member != null) {
@@ -211,7 +212,7 @@
 				if (!fComponentIds.contains(component.getSymbolicName()) || component.equals(reference.getMember().getApiComponent())) {
 					return false;
 				}
-				if (isIllegalUse(reference) || (includesAPI() && includesInternal())) {
+				if (isIllegalUse(reference, monitor) || (includesAPI() && includesInternal())) {
 					return true;
 				}
 				IApiAnnotations annots = component.getApiDescription().resolveAnnotations(member.getHandle());
@@ -226,6 +227,7 @@
 			}
 		} catch (CoreException ce) {
 			ApiPlugin.log(ce);
+			AbstractProblemDetector.checkIfDisposed(reference.getMember().getApiComponent(), monitor);
 		}
 		return false;
 	}
@@ -238,14 +240,17 @@
 	 * @return true if the reference is illegal use false otherwise
 	 * @since 1.1
 	 */
-	boolean isIllegalUse(IReference reference) {
+	boolean isIllegalUse(IReference reference, IProgressMonitor monitor) {
 		IApiProblemDetector[] detectors = fAnalyzer.getProblemDetectors(reference.getReferenceKind());
 		for (IApiProblemDetector detector : detectors) {
-			if (detector.considerReference(reference)) {
+			if (monitor.isCanceled()) {
+				break;
+			}
+			if (detector.considerReference(reference, monitor)) {
 				Reference ref = (Reference) reference;
 				ref.setFlags(IReference.F_ILLEGAL);
 				try {
-					IApiProblem pb = ((AbstractProblemDetector) detector).checkAndCreateProblem(reference);
+					IApiProblem pb = ((AbstractProblemDetector) detector).checkAndCreateProblem(reference, monitor);
 					if (pb != null && !isFiltered(pb)) {
 						ref.addProblems(pb);
 					} else {
@@ -253,6 +258,7 @@
 					}
 				} catch (CoreException e) {
 					ApiPlugin.log(e);
+					AbstractProblemDetector.checkIfDisposed(reference.getMember().getApiComponent(), monitor);
 				}
 				return true;
 			}
diff --git a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/util/Util.java b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/util/Util.java
index 6d5e9c0..2c43fe6 100644
--- a/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/util/Util.java
+++ b/apitools/org.eclipse.pde.api.tools/src/org/eclipse/pde/api/tools/internal/util/Util.java
@@ -1377,7 +1377,7 @@
 			try {
 				methods = type.getMethods();
 			} catch (JavaModelException e) {
-				ApiPlugin.log(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, NLS.bind("Unable to retrieve methods for {0}", type.getFullyQualifiedName()), e)); //$NON-NLS-1$
+				ApiPlugin.log(Status.error(NLS.bind("Unable to retrieve methods for {0}", type.getFullyQualifiedName()), e)); //$NON-NLS-1$
 				return null;
 			}
 		}
@@ -1408,8 +1408,7 @@
 		for (IMethod m : methods) {
 			sb.append('\n').append(m.getHandleIdentifier());
 		}
-		ApiPlugin.log(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, NLS.bind(UtilMessages.Util_6, new String[] {
-				selector, descriptor }) + sb.toString()));
+		ApiPlugin.log(Status.error(NLS.bind(UtilMessages.Util_6, selector, descriptor) + sb));
 		// do not default to the enclosing type - see bug 224713
 		return null;
 	}
@@ -1948,7 +1947,7 @@
 				File outFile = new File(destDir, filePath);
 				String outFileCanonicalPath = outFile.getCanonicalPath();
 				if (!outFileCanonicalPath.startsWith(destDirCanonicalPath + File.separator)) {
-					throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, MessageFormat.format("Entry is outside of the target dir: : {0}", filePath), null)); //$NON-NLS-1$
+					throw new CoreException(Status.error(MessageFormat.format("Entry is outside of the target dir: : {0}", filePath), null)); //$NON-NLS-1$
 				}
 				try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outFile))) {
 					int n = 0;
diff --git a/apitools/pom.xml b/apitools/pom.xml
index 5f75fbc..ea1bdc9 100644
--- a/apitools/pom.xml
+++ b/apitools/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
   </parent>
   <artifactId>eclipse.pde.ui.apitools</artifactId>
diff --git a/ds/org.eclipse.pde.ds.annotations.tests/META-INF/MANIFEST.MF b/ds/org.eclipse.pde.ds.annotations.tests/META-INF/MANIFEST.MF
index 1596213..bea0463 100644
--- a/ds/org.eclipse.pde.ds.annotations.tests/META-INF/MANIFEST.MF
+++ b/ds/org.eclipse.pde.ds.annotations.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: DS Annotations Tests
 Bundle-SymbolicName: org.eclipse.pde.ds.annotations.tests
-Bundle-Version: 1.2.0.qualifier
+Bundle-Version: 1.2.100.qualifier
 Bundle-Activator: org.eclipse.pde.ds.internal.annotations.tests.Activator
 Bundle-Vendor: Eclipse.org
 Bundle-RequiredExecutionEnvironment: JavaSE-11
diff --git a/ds/org.eclipse.pde.ds.annotations.tests/pom.xml b/ds/org.eclipse.pde.ds.annotations.tests/pom.xml
index c3d9841..97c2859 100644
--- a/ds/org.eclipse.pde.ds.annotations.tests/pom.xml
+++ b/ds/org.eclipse.pde.ds.annotations.tests/pom.xml
@@ -13,12 +13,11 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>tests-pom</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../tests-pom/</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ds.annotations.tests</artifactId>
-  <version>1.2.0-SNAPSHOT</version>
+  <version>1.2.100-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
 </project>
diff --git a/ds/org.eclipse.pde.ds.annotations.tests/src/org/eclipse/pde/ds/internal/annotations/tests/AllDSAnnotationsTests.java b/ds/org.eclipse.pde.ds.annotations.tests/src/org/eclipse/pde/ds/internal/annotations/tests/AllDSAnnotationsTests.java
index 8c02440..9624eaa 100644
--- a/ds/org.eclipse.pde.ds.annotations.tests/src/org/eclipse/pde/ds/internal/annotations/tests/AllDSAnnotationsTests.java
+++ b/ds/org.eclipse.pde.ds.annotations.tests/src/org/eclipse/pde/ds/internal/annotations/tests/AllDSAnnotationsTests.java
@@ -75,7 +75,7 @@
 							projectFile.renameTo(projectLocation.resolve(".project").toFile());
 						}
 					} catch (IOException e) {
-						throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error copying test project content.", e));
+						throw new CoreException(Status.error("Error copying test project content.", e));
 					}
 
 					project.create(monitor);
diff --git a/ds/org.eclipse.pde.ds.annotations/META-INF/MANIFEST.MF b/ds/org.eclipse.pde.ds.annotations/META-INF/MANIFEST.MF
index ddcb819..6efb12d 100644
--- a/ds/org.eclipse.pde.ds.annotations/META-INF/MANIFEST.MF
+++ b/ds/org.eclipse.pde.ds.annotations/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-SymbolicName: org.eclipse.pde.ds.annotations;singleton:=true
-Bundle-Version: 1.2.200.qualifier
+Bundle-Version: 1.2.300.qualifier
 Bundle-Activator: org.eclipse.pde.ds.internal.annotations.Activator
 Bundle-Vendor: %Bundle-Vendor
 Require-Bundle: org.eclipse.ui;bundle-version="[3.105.0,4.0.0)",
diff --git a/ds/org.eclipse.pde.ds.annotations/pom.xml b/ds/org.eclipse.pde.ds.annotations/pom.xml
index 28ca94e..d8e0ba5 100644
--- a/ds/org.eclipse.pde.ds.annotations/pom.xml
+++ b/ds/org.eclipse.pde.ds.annotations/pom.xml
@@ -15,12 +15,11 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ds.annotations</artifactId>
-  <version>1.2.200-SNAPSHOT</version>
+  <version>1.2.300-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/Activator.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/Activator.java
index 601462e..bdad3a1 100644
--- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/Activator.java
+++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/Activator.java
@@ -90,7 +90,7 @@
 	}
 
 	public static void log(Throwable e) {
-		log(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, e.getMessage(), e));
+		log(Status.error(e.getMessage(), e));
 	}
 
 	void listenForClasspathPreferenceChanges(IJavaProject project) {
diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/AnnotationVisitor.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/AnnotationVisitor.java
index 5621632..e08f199 100644
--- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/AnnotationVisitor.java
+++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/AnnotationVisitor.java
@@ -45,7 +45,6 @@
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.IType;
@@ -370,7 +369,7 @@
 				try {
 					oldFile.move(file.getFullPath(), true, true, null);
 				} catch (CoreException e) {
-					Activator.log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, String.format("Unable to move model file from '%s' to '%s'.", oldPath, file.getFullPath()), e)); //$NON-NLS-1$
+					Activator.log(Status.warning(String.format("Unable to move model file from '%s' to '%s'.", oldPath, file.getFullPath()), e)); //$NON-NLS-1$
 				}
 			}
 		}
@@ -478,7 +477,7 @@
 			LinkedModeModel.closeAllModels(document);
 			edit.apply(document);
 		} catch (MalformedTreeException | BadLocationException e) {
-			throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error applying changes to component model.", e)); //$NON-NLS-1$
+			throw new CoreException(Status.error("Error applying changes to component model.", e)); //$NON-NLS-1$
 		} finally {
 			if (session != null) {
 				((IDocumentExtension4) document).stopRewriteSession(session);
diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ComponentPropertyTester.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ComponentPropertyTester.java
index bf65779..ccf759b 100644
--- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ComponentPropertyTester.java
+++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ComponentPropertyTester.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2015, 2016 Ecliptical Software Inc. and others.
+ * Copyright (c) 2015, 2022 Ecliptical Software Inc. and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -13,7 +13,6 @@
  *******************************************************************************/
 package org.eclipse.pde.ds.internal.annotations;
 
-import java.awt.Component;
 
 import org.eclipse.core.expressions.PropertyTester;
 import org.eclipse.core.resources.ProjectScope;
diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationCompilationParticipant.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationCompilationParticipant.java
index 18a2904..bafb5d8 100644
--- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationCompilationParticipant.java
+++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationCompilationParticipant.java
@@ -225,7 +225,7 @@
 			try {
 				state = loadState(project.getProject());
 			} catch (IOException e) {
-				Activator.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error loading project state.", e)); //$NON-NLS-1$
+				Activator.log(Status.error("Error loading project state.", e)); //$NON-NLS-1$
 			}
 
 			if (state == null) {
diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationPropertyPage.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationPropertyPage.java
index 1134dc3..179ccae 100644
--- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationPropertyPage.java
+++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationPropertyPage.java
@@ -19,7 +19,6 @@
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.ProjectScope;
 import org.eclipse.core.runtime.IAdaptable;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
@@ -417,7 +416,7 @@
 				prefs.remove(key);
 			}
 		} catch (BackingStoreException e) {
-			Activator.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unable to restore default values.", e)); //$NON-NLS-1$
+			Activator.log(Status.error("Unable to restore default values.", e)); //$NON-NLS-1$
 		}
 
 		refreshWidgets();
@@ -436,7 +435,7 @@
 				try {
 					prefs.clear();
 				} catch (BackingStoreException e) {
-					Activator.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unable to reset project preferences.", e)); //$NON-NLS-1$
+					Activator.log(Status.error("Unable to reset project preferences.", e)); //$NON-NLS-1$
 				}
 
 				prefs = null;
@@ -473,7 +472,7 @@
 		try {
 			wcManager.applyChanges();
 		} catch (BackingStoreException e) {
-			Activator.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unable to save preferences.", e)); //$NON-NLS-1$
+			Activator.log(Status.error("Unable to save preferences.", e)); //$NON-NLS-1$
 			return false;
 		}
 
diff --git a/ds/org.eclipse.pde.ds.core/META-INF/MANIFEST.MF b/ds/org.eclipse.pde.ds.core/META-INF/MANIFEST.MF
index 0eafdf8..7fcffdd 100644
--- a/ds/org.eclipse.pde.ds.core/META-INF/MANIFEST.MF
+++ b/ds/org.eclipse.pde.ds.core/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.pde.ds.core;singleton:=true
-Bundle-Version: 1.2.300.qualifier
+Bundle-Version: 1.2.400.qualifier
 Bundle-Activator: org.eclipse.pde.internal.ds.core.Activator
 Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.11.0,4.0.0)",
  org.eclipse.core.filebuffers;bundle-version="[3.3.0,4.0.0)",
diff --git a/ds/org.eclipse.pde.ds.core/pom.xml b/ds/org.eclipse.pde.ds.core/pom.xml
index 563d978..b042a17 100644
--- a/ds/org.eclipse.pde.ds.core/pom.xml
+++ b/ds/org.eclipse.pde.ds.core/pom.xml
@@ -14,12 +14,11 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ds.core</artifactId>
-  <version>1.2.300-SNAPSHOT</version>
+  <version>1.2.400-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/ds/org.eclipse.pde.ds.core/src/org/eclipse/pde/internal/ds/core/Activator.java b/ds/org.eclipse.pde.ds.core/src/org/eclipse/pde/internal/ds/core/Activator.java
index 72875f6..6565a84 100644
--- a/ds/org.eclipse.pde.ds.core/src/org/eclipse/pde/internal/ds/core/Activator.java
+++ b/ds/org.eclipse.pde.ds.core/src/org/eclipse/pde/internal/ds/core/Activator.java
@@ -69,7 +69,7 @@
 		if (e instanceof CoreException) {
 			status = ((CoreException) e).getStatus();
 		} else if (e.getMessage() != null) {
-			status = new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, e.getMessage(), e);
+			status = Status.error(e.getMessage(), e);
 		}
 		log(status);
 	}
diff --git a/ds/org.eclipse.pde.ds.lib/pom.xml b/ds/org.eclipse.pde.ds.lib/pom.xml
index b346ee8..9447ac0 100644
--- a/ds/org.eclipse.pde.ds.lib/pom.xml
+++ b/ds/org.eclipse.pde.ds.lib/pom.xml
@@ -15,11 +15,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ds.lib</artifactId>
   <version>1.1.500-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ds/org.eclipse.pde.ds.tests/pom.xml b/ds/org.eclipse.pde.ds.tests/pom.xml
index 6c16cbb..a70db1d 100644
--- a/ds/org.eclipse.pde.ds.tests/pom.xml
+++ b/ds/org.eclipse.pde.ds.tests/pom.xml
@@ -14,11 +14,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>tests-pom</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../tests-pom/</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ds.tests</artifactId>
   <version>1.2.0-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
diff --git a/ds/org.eclipse.pde.ds.ui/pom.xml b/ds/org.eclipse.pde.ds.ui/pom.xml
index 6a00cfb..f86e7a9 100644
--- a/ds/org.eclipse.pde.ds.ui/pom.xml
+++ b/ds/org.eclipse.pde.ds.ui/pom.xml
@@ -14,11 +14,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ds.ui</artifactId>
   <version>1.2.200-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/Activator.java b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/Activator.java
index d8f12be..c208d68 100644
--- a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/Activator.java
+++ b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/Activator.java
@@ -119,8 +119,7 @@
 				message = e.getMessage();
 			if (message == null)
 				message = e.toString();
-			status = new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK,
-					message, e);
+			status = Status.error(message, e);
 		}
 		ResourcesPlugin.getPlugin().getLog().log(status);
 		Display display = Display.getCurrent() != null ? Display.getCurrent()
diff --git a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/dialogs/DSEditPropertiesDialog.java b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/dialogs/DSEditPropertiesDialog.java
index 33a33b2..2186eeb 100644
--- a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/dialogs/DSEditPropertiesDialog.java
+++ b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/dialogs/DSEditPropertiesDialog.java
@@ -20,7 +20,6 @@
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.IJavaElement;
 import org.eclipse.jdt.core.IJavaProject;
@@ -227,11 +226,9 @@
 			if (selection != null
 					&& selection.length > 0
 					&& (selection[0] instanceof IFile || selection[0] instanceof IContainer))
-				return new Status(IStatus.OK, Activator.PLUGIN_ID,
-						IStatus.OK, "", null); //$NON-NLS-1$
+				return Status.OK_STATUS;
 
-			return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
-					IStatus.ERROR, "", null); //$NON-NLS-1$
+			return Status.error("", null); //$NON-NLS-1$
 		});
 		if (dialog.open() == Window.OK) {
 			IResource res = (IResource) dialog.getFirstResult();
diff --git a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/dialogs/DSEditPropertyDialog.java b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/dialogs/DSEditPropertyDialog.java
index b3282d1..0862f4b 100644
--- a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/dialogs/DSEditPropertyDialog.java
+++ b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/dialogs/DSEditPropertyDialog.java
@@ -23,7 +23,6 @@
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.IJavaElement;
 import org.eclipse.jdt.core.IJavaProject;
@@ -350,11 +349,9 @@
 			if (selection != null
 					&& selection.length > 0
 					&& (selection[0] instanceof IFile || selection[0] instanceof IContainer))
-				return new Status(IStatus.OK, Activator.PLUGIN_ID,
-						IStatus.OK, "", null); //$NON-NLS-1$
+				return Status.OK_STATUS;
 
-			return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
-					IStatus.ERROR, "", null); //$NON-NLS-1$
+			return Status.error("", null); //$NON-NLS-1$
 		});
 		if (dialog.open() == Window.OK) {
 			IResource res = (IResource) dialog.getFirstResult();
diff --git a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/sections/DSPropertiesSection.java b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/sections/DSPropertiesSection.java
index 256481b..460a8a3 100644
--- a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/sections/DSPropertiesSection.java
+++ b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/editor/sections/DSPropertiesSection.java
@@ -24,7 +24,6 @@
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.viewers.IStructuredContentProvider;
@@ -372,11 +371,9 @@
 		dialog.setValidator(selection -> {
 			if (selection != null && selection.length > 0
 					&& selection[0] instanceof IFile)
-				return new Status(IStatus.OK, Activator.PLUGIN_ID,
-						IStatus.OK, "", null); //$NON-NLS-1$
+				return Status.OK_STATUS;
 
-			return new Status(IStatus.ERROR, Activator.PLUGIN_ID,
-					IStatus.ERROR, "", null); //$NON-NLS-1$
+			return Status.error("", null); //$NON-NLS-1$
 		});
 		if (dialog.open() == Window.OK) {
 			IResource res = (IResource) dialog.getFirstResult();
diff --git a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/messages.properties b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/messages.properties
index a2764f3..a31e8f6 100644
--- a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/messages.properties
+++ b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/messages.properties
@@ -21,7 +21,7 @@
 DSComponentDetails_modifiedEntry=Modified:
 DSComponentDetails_modifiedTooltip=The modified method name
 DSComponentDetails_factoryEntry=Factory ID:
-DSComponentDetails_configurationPolicy=Configuration Policy:
+DSComponentDetails_configurationPolicy=Configuration policy:
 DSCreationOperation_title=Creating Component File...
 
 DSFileWizardPage_description=Create a new component definition.
diff --git a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/wizards/DSFileWizardPage.java b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/wizards/DSFileWizardPage.java
index 8eea815..e34d851 100644
--- a/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/wizards/DSFileWizardPage.java
+++ b/ds/org.eclipse.pde.ds.ui/src/org/eclipse/pde/internal/ds/ui/wizards/DSFileWizardPage.java
@@ -287,7 +287,7 @@
 
 	@Override
 	protected IStatus validateLinkedResource() {
-		return new Status(IStatus.OK, Activator.PLUGIN_ID, IStatus.OK, "", null); //$NON-NLS-1$
+		return Status.OK_STATUS;
 	}
 
 	@Override
diff --git a/ds/org.eclipse.pde.ds1_2.lib/pom.xml b/ds/org.eclipse.pde.ds1_2.lib/pom.xml
index 637757f..bfa2eea 100644
--- a/ds/org.eclipse.pde.ds1_2.lib/pom.xml
+++ b/ds/org.eclipse.pde.ds1_2.lib/pom.xml
@@ -16,11 +16,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ds1_2.lib</artifactId>
   <version>1.0.500-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/features/org.eclipse.pde-feature/pom.xml b/features/org.eclipse.pde-feature/pom.xml
index e61ac6c..6e8e464 100644
--- a/features/org.eclipse.pde-feature/pom.xml
+++ b/features/org.eclipse.pde-feature/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
diff --git a/features/org.eclipse.pde.unittest.junit-feature/feature.xml b/features/org.eclipse.pde.unittest.junit-feature/feature.xml
index 485aace..06b89e1 100644
--- a/features/org.eclipse.pde.unittest.junit-feature/feature.xml
+++ b/features/org.eclipse.pde.unittest.junit-feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.pde.unittest.junit"
       label="%featureName"
-      version="1.0.0.qualifier"
+      version="1.0.100.qualifier"
       provider-name="%providerName"
       license-feature="org.eclipse.license"
       license-feature-version="0.0.0">
diff --git a/features/org.eclipse.pde.unittest.junit-feature/pom.xml b/features/org.eclipse.pde.unittest.junit-feature/pom.xml
index 58a408a..29cb77c 100644
--- a/features/org.eclipse.pde.unittest.junit-feature/pom.xml
+++ b/features/org.eclipse.pde.unittest.junit-feature/pom.xml
@@ -13,13 +13,13 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
   <groupId>org.eclipse.pde.unittest.junit.feature</groupId>
   <artifactId>org.eclipse.pde.unittest.junit</artifactId>
-  <version>1.0.0-SNAPSHOT</version>
+  <version>1.0.100-SNAPSHOT</version>
   <packaging>eclipse-feature</packaging>
 
   <build>
diff --git a/pom.xml b/pom.xml
index 328f871..78e638b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <relativePath>../eclipse-platform-parent</relativePath>
   </parent>
 
-  <groupId>eclipse.pde.ui</groupId>
+  <groupId>org.eclipse.pde</groupId>
   <artifactId>eclipse.pde.ui</artifactId>
   <packaging>pom</packaging>
 
diff --git a/releng/org.eclipse.pde.ui.setup/PDE.setup b/releng/org.eclipse.pde.ui.setup/PDE.setup
index 25b01de..c98eeeb 100644
--- a/releng/org.eclipse.pde.ui.setup/PDE.setup
+++ b/releng/org.eclipse.pde.ui.setup/PDE.setup
@@ -98,9 +98,9 @@
       <repositoryList
           name="CBI">
         <repository
-            url="http://download.eclipse.org/cbi/updates/license"/>
+            url="https://download.eclipse.org/cbi/updates/license"/>
         <repository
-            url="http://download.eclipse.org/reddeer/releases/3.5.0"/>
+            url="https://download.eclipse.org/reddeer/releases/3.5.0"/>
         <repository
             url="https://download.eclipse.org/reddeer/releases/latest"/>
       </repositoryList>
diff --git a/tests-pom/pom.xml b/tests-pom/pom.xml
index 58798f5..523763d 100644
--- a/tests-pom/pom.xml
+++ b/tests-pom/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
   </parent>
   <artifactId>tests-pom</artifactId>
diff --git a/ua/org.eclipse.pde.ua.core/pom.xml b/ua/org.eclipse.pde.ua.core/pom.xml
index 7de4be4..13ea3a8 100644
--- a/ua/org.eclipse.pde.ua.core/pom.xml
+++ b/ua/org.eclipse.pde.ua.core/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
diff --git a/ua/org.eclipse.pde.ua.tests/pom.xml b/ua/org.eclipse.pde.ua.tests/pom.xml
index feebc3c..4fc7a2a 100644
--- a/ua/org.eclipse.pde.ua.tests/pom.xml
+++ b/ua/org.eclipse.pde.ua.tests/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>tests-pom</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../tests-pom/</relativePath>
   </parent>
diff --git a/ua/org.eclipse.pde.ua.ui/META-INF/MANIFEST.MF b/ua/org.eclipse.pde.ua.ui/META-INF/MANIFEST.MF
index 910db3b..1237155 100644
--- a/ua/org.eclipse.pde.ua.ui/META-INF/MANIFEST.MF
+++ b/ua/org.eclipse.pde.ua.ui/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.pde.ua.ui;singleton:=true
-Bundle-Version: 1.2.200.qualifier
+Bundle-Version: 1.2.300.qualifier
 Bundle-Activator: org.eclipse.pde.internal.ua.ui.PDEUserAssistanceUIPlugin
 Require-Bundle: org.eclipse.ui;bundle-version="[3.4.0,4.0.0)",
  org.eclipse.core.runtime;bundle-version="[3.11.0,4.0.0)",
diff --git a/ua/org.eclipse.pde.ua.ui/pom.xml b/ua/org.eclipse.pde.ua.ui/pom.xml
index 5db1a94..1c1bd51 100644
--- a/ua/org.eclipse.pde.ua.ui/pom.xml
+++ b/ua/org.eclipse.pde.ua.ui/pom.xml
@@ -14,13 +14,13 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
   <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ua.ui</artifactId>
-  <version>1.2.200-SNAPSHOT</version>
+  <version>1.2.300-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 
   <properties>
diff --git a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/PDEUserAssistanceUIPlugin.java b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/PDEUserAssistanceUIPlugin.java
index 35ed90d..b5af680 100644
--- a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/PDEUserAssistanceUIPlugin.java
+++ b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/PDEUserAssistanceUIPlugin.java
@@ -94,8 +94,7 @@
 				message = e.getMessage();
 			if (message == null)
 				message = e.toString();
-			status = new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK,
-					message, e);
+			status = Status.error(message, e);
 		}
 		ResourcesPlugin.getPlugin().getLog().log(status);
 		Display display = Display.getCurrent() != null ? Display.getCurrent()
diff --git a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/editor/cheatsheet/comp/CompCSFileValidator.java b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/editor/cheatsheet/comp/CompCSFileValidator.java
index 3ab9ea8..2b1dc80 100755
--- a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/editor/cheatsheet/comp/CompCSFileValidator.java
+++ b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/editor/cheatsheet/comp/CompCSFileValidator.java
@@ -35,19 +35,19 @@
 
 		// Ensure something was selected
 		if (selection.length == 0) {
-			return errorStatus(""); //$NON-NLS-1$
+			return Status.error(""); //$NON-NLS-1$
 		}
 		// Ensure we have a file
 		if ((selection[0] instanceof IFile) == false) {
-			return errorStatus(""); //$NON-NLS-1$
+			return Status.error(""); //$NON-NLS-1$
 		}
 		IFile file = (IFile) selection[0];
 		// Ensure we have a simple cheat sheet file
 		if (isSimpleCSFile(file) == false) {
-			return errorStatus(Messages.CompCSFileValidator_0);
+			return Status.error(Messages.CompCSFileValidator_0);
 		}
 		// If we got this far, we have a valid file
-		return okStatus(""); //$NON-NLS-1$
+		return Status.OK_STATUS;
 
 	}
 
@@ -62,15 +62,4 @@
 		}
 		return false;
 	}
-
-	private IStatus errorStatus(String message) {
-		return new Status(IStatus.ERROR, PDEUserAssistanceUIPlugin.PLUGIN_ID,
-				IStatus.ERROR, message, null);
-	}
-
-	private IStatus okStatus(String message) {
-		return new Status(IStatus.OK, PDEUserAssistanceUIPlugin.PLUGIN_ID,
-				IStatus.OK, message, null);
-	}
-
 }
diff --git a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/editor/toc/TocFileValidator.java b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/editor/toc/TocFileValidator.java
index 4075309..789823f 100755
--- a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/editor/toc/TocFileValidator.java
+++ b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/editor/toc/TocFileValidator.java
@@ -18,7 +18,6 @@
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.core.IBaseModel;
-import org.eclipse.pde.internal.ua.ui.PDEUserAssistanceUIPlugin;
 import org.eclipse.ui.dialogs.ISelectionStatusValidator;
 
 public class TocFileValidator implements ISelectionStatusValidator {
@@ -33,34 +32,25 @@
 
 		// Ensure something was selected
 		if (selection.length == 0) {
-			return errorStatus(""); //$NON-NLS-1$
+			return Status.error(""); //$NON-NLS-1$
 		}
 		// Ensure we have a file
 		if ((selection[0] instanceof IFile) == false) {
-			return errorStatus(""); //$NON-NLS-1$
+			return Status.error(""); //$NON-NLS-1$
 		}
 		IFile file = (IFile) selection[0];
 		// Ensure we have a TOC file
 		if (!HelpEditorUtil.isTOCFile(file.getFullPath())) {
-			return errorStatus(TocMessages.TocFileValidator_errorMessage1);
+			return Status.error(TocMessages.TocFileValidator_errorMessage1);
 		}
 
 		//Ensure that the TOC file selected isn't the current file
 		if (HelpEditorUtil.isCurrentResource(file.getFullPath(), fModel)) {
-			return errorStatus(TocMessages.TocFileValidator_errorMessage2);
+			return Status.error(TocMessages.TocFileValidator_errorMessage2);
 		}
 
 		// If we got this far, we have a valid file
-		return okStatus(""); //$NON-NLS-1$
+		return Status.OK_STATUS;
 
 	}
-
-	private IStatus errorStatus(String message) {
-		return new Status(IStatus.ERROR, PDEUserAssistanceUIPlugin.PLUGIN_ID, IStatus.ERROR, message, null);
-	}
-
-	private IStatus okStatus(String message) {
-		return new Status(IStatus.OK, PDEUserAssistanceUIPlugin.PLUGIN_ID, IStatus.OK, message, null);
-	}
-
 }
diff --git a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/cheatsheet/RegisterCSOperation.java b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/cheatsheet/RegisterCSOperation.java
index 78244f9..7147cb3 100755
--- a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/cheatsheet/RegisterCSOperation.java
+++ b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/cheatsheet/RegisterCSOperation.java
@@ -45,8 +45,6 @@
 import org.eclipse.pde.internal.core.util.PDETextHelper;
 import org.eclipse.pde.internal.ua.core.cheatsheet.simple.ISimpleCSConstants;
 import org.eclipse.pde.internal.ua.core.icheatsheet.comp.ICompCSConstants;
-import org.eclipse.pde.internal.ua.ui.PDEUserAssistanceUIPlugin;
-import org.eclipse.pde.internal.ui.IPDEUIConstants;
 import org.eclipse.pde.internal.ui.util.ModelModification;
 import org.eclipse.pde.internal.ui.util.PDEModelUtility;
 import org.eclipse.swt.widgets.Shell;
@@ -117,8 +115,8 @@
 		// Note: This is not accurate, we are validating the plugin.xml file
 		// but not the manifest.mf file
 		IStatus status = ResourcesPlugin.getWorkspace().validateEdit(new IFile[] {file}, fShell);
-		if (status.getSeverity() != IStatus.OK) {
-			throw new CoreException(new Status(IStatus.ERROR, PDEUserAssistanceUIPlugin.PLUGIN_ID, IStatus.ERROR, CSWizardMessages.RegisterCSOperation_errorMessage, null));
+		if (!status.isOK()) {
+			throw new CoreException(Status.error(CSWizardMessages.RegisterCSOperation_errorMessage, null));
 		}
 		// Perform the modification of the plugin manifest file
 		ModelModification mod = new ModelModification(fRegisterCSData.getPluginProject()) {
@@ -341,8 +339,8 @@
 		// Note: This is not accurate, we are validating the plugin.xml file rather
 		// than the manifest file
 		IStatus status = ResourcesPlugin.getWorkspace().validateEdit(new IFile[] {file}, fShell);
-		if (status.getSeverity() != IStatus.OK) {
-			throw new CoreException(new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, IStatus.ERROR, CSWizardMessages.RegisterCSOperation_errorMessage2, null));
+		if (!status.isOK()) {
+			throw new CoreException(Status.error(CSWizardMessages.RegisterCSOperation_errorMessage2, null));
 		}
 		// Perform the modification of the manifest file
 		ModelModification mod = new ModelModification(fRegisterCSData.getPluginProject()) {
diff --git a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/ctxhelp/RegisterCtxHelpOperation.java b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/ctxhelp/RegisterCtxHelpOperation.java
index 00a6d0b..0de1e65 100644
--- a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/ctxhelp/RegisterCtxHelpOperation.java
+++ b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/ctxhelp/RegisterCtxHelpOperation.java
@@ -43,7 +43,6 @@
 import org.eclipse.pde.internal.core.text.bundle.RequireBundleHeader;
 import org.eclipse.pde.internal.core.util.PDETextHelper;
 import org.eclipse.pde.internal.ua.core.ctxhelp.ICtxHelpConstants;
-import org.eclipse.pde.internal.ua.ui.PDEUserAssistanceUIPlugin;
 import org.eclipse.pde.internal.ua.ui.editor.ctxhelp.CtxHelpEditor;
 import org.eclipse.pde.internal.ui.util.ModelModification;
 import org.eclipse.pde.internal.ui.util.PDEModelUtility;
@@ -105,11 +104,8 @@
 			throws CoreException {
 		IStatus status = ResourcesPlugin.getWorkspace().validateEdit(
 				new IFile[] { file }, fShell);
-		if (status.getSeverity() != IStatus.OK) {
-			throw new CoreException(new Status(IStatus.ERROR,
-					PDEUserAssistanceUIPlugin.PLUGIN_ID, IStatus.ERROR,
-					CtxWizardMessages.RegisterCtxHelpOperation_errorMessage1,
-					null));
+		if (!status.isOK()) {
+			throw new CoreException(Status.error(CtxWizardMessages.RegisterCtxHelpOperation_errorMessage1));
 		}
 		// Perform the modification of the plugin manifest file
 		ModelModification mod = new ModelModification(fProject) {
@@ -218,11 +214,8 @@
 		// than the manifest file
 		IStatus status = ResourcesPlugin.getWorkspace().validateEdit(
 				new IFile[] { file }, fShell);
-		if (status.getSeverity() != IStatus.OK) {
-			throw new CoreException(new Status(IStatus.ERROR,
-					PDEUserAssistanceUIPlugin.PLUGIN_ID, IStatus.ERROR,
-					CtxWizardMessages.RegisterCtxHelpOperation_errorMessage2,
-					null));
+		if (!status.isOK()) {
+			throw new CoreException(Status.error(CtxWizardMessages.RegisterCtxHelpOperation_errorMessage2));
 		}
 		// Perform the modification of the manifest file
 		ModelModification mod = new ModelModification(fProject) {
diff --git a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/toc/RegisterTocOperation.java b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/toc/RegisterTocOperation.java
index 25a1d07..d84fb81 100644
--- a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/toc/RegisterTocOperation.java
+++ b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/toc/RegisterTocOperation.java
@@ -44,7 +44,6 @@
 import org.eclipse.pde.internal.core.text.bundle.RequireBundleHeader;
 import org.eclipse.pde.internal.core.util.PDETextHelper;
 import org.eclipse.pde.internal.ua.core.toc.ITocConstants;
-import org.eclipse.pde.internal.ua.ui.PDEUserAssistanceUIPlugin;
 import org.eclipse.pde.internal.ui.util.ModelModification;
 import org.eclipse.pde.internal.ui.util.PDEModelUtility;
 import org.eclipse.swt.widgets.Shell;
@@ -116,8 +115,8 @@
 		// Note: This is not accurate, we are validating the plugin.xml file
 		// but not the manifest.mf file
 		IStatus status = ResourcesPlugin.getWorkspace().validateEdit(new IFile[] {file}, fShell);
-		if (status.getSeverity() != IStatus.OK) {
-			throw new CoreException(new Status(IStatus.ERROR, PDEUserAssistanceUIPlugin.PLUGIN_ID, IStatus.ERROR, TocWizardMessages.RegisterTocOperation_errorMessage1, null));
+		if (!status.isOK()) {
+			throw new CoreException(Status.error(TocWizardMessages.RegisterTocOperation_errorMessage1));
 		}
 		// Perform the modification of the plugin manifest file
 		ModelModification mod = new ModelModification(fPage.getPluginProject()) {
@@ -287,8 +286,8 @@
 		// Note: This is not accurate, we are validating the plugin.xml file rather
 		// than the manifest file
 		IStatus status = ResourcesPlugin.getWorkspace().validateEdit(new IFile[] {file}, fShell);
-		if (status.getSeverity() != IStatus.OK) {
-			throw new CoreException(new Status(IStatus.ERROR, PDEUserAssistanceUIPlugin.PLUGIN_ID, IStatus.ERROR, TocWizardMessages.RegisterTocOperation_errorMessage2, null));
+		if (!status.isOK()) {
+			throw new CoreException(Status.error(TocWizardMessages.RegisterTocOperation_errorMessage2));
 		}
 		// Perform the modification of the manifest file
 		ModelModification mod = new ModelModification(fPage.getPluginProject()) {
diff --git a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/toc/TocHTMLWizardPage.java b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/toc/TocHTMLWizardPage.java
index 4767613..8612cc5 100644
--- a/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/toc/TocHTMLWizardPage.java
+++ b/ua/org.eclipse.pde.ua.ui/src/org/eclipse/pde/internal/ua/ui/wizards/toc/TocHTMLWizardPage.java
@@ -19,7 +19,6 @@
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.osgi.util.NLS;
-import org.eclipse.pde.internal.ua.ui.PDEUserAssistanceUIPlugin;
 import org.eclipse.pde.internal.ua.ui.editor.toc.HelpEditorUtil;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.dialogs.WizardNewFileCreationPage;
@@ -64,7 +63,7 @@
 
 	@Override
 	protected IStatus validateLinkedResource() {
-		return new Status(IStatus.OK, PDEUserAssistanceUIPlugin.PLUGIN_ID, IStatus.OK, "", null); //$NON-NLS-1$
+		return Status.OK_STATUS;
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.core/pom.xml b/ui/org.eclipse.pde.core/pom.xml
index 8d2039f..5f7f49f 100644
--- a/ui/org.eclipse.pde.core/pom.xml
+++ b/ui/org.eclipse.pde.core/pom.xml
@@ -14,11 +14,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.core</artifactId>
   <version>3.15.100-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/TargetPlatform.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/TargetPlatform.java
index 1bc4349..714e112 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/TargetPlatform.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/plugin/TargetPlatform.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2007, 2019 IBM Corporation and others.
+ *  Copyright (c) 2007, 2022 IBM Corporation and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -17,6 +17,8 @@
 import java.net.URL;
 import java.util.Properties;
 import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Path;
@@ -107,16 +109,7 @@
 	 * @return the target operating system
 	 */
 	public static String getOS() {
-		try {
-			ITargetDefinition target = TargetPlatformService.getDefault().getWorkspaceTargetDefinition();
-			String result = target.getOS();
-			if (result != null) {
-				return result;
-			}
-		} catch (CoreException e) {
-			PDECore.log(e);
-		}
-		return Platform.getOS();
+		return getTargetProperty(ITargetDefinition::getOS, Platform::getOS);
 	}
 
 	/**
@@ -127,16 +120,7 @@
 	 * @return the target windowing system
 	 */
 	public static String getWS() {
-		try {
-			ITargetDefinition target = TargetPlatformService.getDefault().getWorkspaceTargetDefinition();
-			String result = target.getWS();
-			if (result != null) {
-				return result;
-			}
-		} catch (CoreException e) {
-			PDECore.log(e);
-		}
-		return Platform.getWS();
+		return getTargetProperty(ITargetDefinition::getWS, Platform::getWS);
 	}
 
 	/**
@@ -146,16 +130,7 @@
 	 * @return the target locale
 	 */
 	public static String getNL() {
-		try {
-			ITargetDefinition target = TargetPlatformService.getDefault().getWorkspaceTargetDefinition();
-			String result = target.getNL();
-			if (result != null) {
-				return result;
-			}
-		} catch (CoreException e) {
-			PDECore.log(e);
-		}
-		return Platform.getNL();
+		return getTargetProperty(ITargetDefinition::getNL, Platform::getNL);
 	}
 
 	/**
@@ -166,16 +141,20 @@
 	 * @return the target system architecture
 	 */
 	public static String getOSArch() {
+		return getTargetProperty(ITargetDefinition::getArch, Platform::getOSArch);
+	}
+
+	private static String getTargetProperty(Function<ITargetDefinition, String> getter, Supplier<String> defaultValue) {
 		try {
 			ITargetDefinition target = TargetPlatformService.getDefault().getWorkspaceTargetDefinition();
-			String result = target.getArch();
+			String result = getter.apply(target);
 			if (result != null) {
 				return result;
 			}
 		} catch (CoreException e) {
 			PDECore.log(e);
 		}
-		return Platform.getOSArch();
+		return defaultValue.get();
 	}
 
 	/**
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/LoadTargetDefinitionJob.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/LoadTargetDefinitionJob.java
index b14b060..dd20524 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/LoadTargetDefinitionJob.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/LoadTargetDefinitionJob.java
@@ -127,7 +127,7 @@
 
 			PDEPreferencesManager preferences = PDECore.getDefault().getPreferencesManager();
 
-			((TargetPlatformService) TargetPlatformService.getDefault()).setWorkspaceTargetDefinition(fTarget); // Must be set before preference so listeners can react
+			((TargetPlatformService) TargetPlatformService.getDefault()).setWorkspaceTargetDefinition(fTarget, false); // Must be set before preference so listeners can react
 			String memento = fTarget.getHandle().getMemento();
 			if (fNone) {
 				memento = ICoreConstants.NO_TARGET;
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/TargetBundle.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/TargetBundle.java
index 0232740..8baec65 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/TargetBundle.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/TargetBundle.java
@@ -63,8 +63,12 @@
 	public static final int STATUS_VERSION_DOES_NOT_EXIST = 101;
 
 	/**
-	 * Status code indicating that a bundle's manifest could not be read, or did not exist.
+	 * Status code indicating that a bundle's manifest could not be read, or did
+	 * not exist.
+	 *
+	 * @deprecated not used anymore
 	 */
+	@Deprecated(forRemoval = true)
 	public static final int STATUS_INVALID_MANIFEST = 102;
 
 	protected BundleInfo fInfo;
@@ -162,7 +166,7 @@
 	 */
 	private void initialize(File file) throws CoreException {
 		if (file == null || !file.exists()) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.TargetFeature_FileDoesNotExist, file)));
+			throw new CoreException(Status.error(NLS.bind(Messages.TargetFeature_FileDoesNotExist, file)));
 		}
 		Map<String, String> manifest = ManifestUtils.loadManifest(file);
 		try {
@@ -188,7 +192,7 @@
 			}
 			fIsFragment = manifest.containsKey(Constants.FRAGMENT_HOST);
 		} catch (BundleException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, STATUS_INVALID_MANIFEST, NLS.bind(Messages.TargetBundle_ErrorReadingManifest, file.getAbsolutePath()), e));
+			throw new CoreException(Status.error(NLS.bind(Messages.TargetBundle_ErrorReadingManifest, file.getAbsolutePath()), e));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/TargetFeature.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/TargetFeature.java
index d091306..9daf580 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/TargetFeature.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/target/TargetFeature.java
@@ -22,13 +22,11 @@
 import java.util.Objects;
 import org.eclipse.core.runtime.Adapters;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.pde.core.IModel;
 import org.eclipse.pde.internal.core.ExternalFeatureModelManager;
 import org.eclipse.pde.internal.core.ICoreConstants;
-import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.ifeature.IFeature;
 import org.eclipse.pde.internal.core.ifeature.IFeatureChild;
 import org.eclipse.pde.internal.core.ifeature.IFeatureImport;
@@ -164,8 +162,7 @@
 	 */
 	private static IFeatureModel loadModel(File file) throws CoreException {
 		if (file == null || !file.exists()) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
-					NLS.bind(Messages.TargetFeature_FileDoesNotExist, file)));
+			throw new CoreException(Status.error(NLS.bind(Messages.TargetFeature_FileDoesNotExist, file)));
 		}
 		File featureXML;
 		if (ICoreConstants.FEATURE_FILENAME_DESCRIPTOR.equalsIgnoreCase(file.getName())) {
@@ -173,8 +170,7 @@
 		} else {
 			featureXML = new File(file, ICoreConstants.FEATURE_FILENAME_DESCRIPTOR);
 			if (!featureXML.exists()) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
-						NLS.bind(Messages.TargetFeature_FileDoesNotExist, featureXML)));
+				throw new CoreException(Status.error(NLS.bind(Messages.TargetFeature_FileDoesNotExist, featureXML)));
 			}
 		}
 		return ExternalFeatureModelManager.createModel(featureXML);
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/AbstractModel.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/AbstractModel.java
index 911da29..9fd27ae 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/AbstractModel.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/AbstractModel.java
@@ -31,7 +31,6 @@
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.PlatformObject;
 import org.eclipse.core.runtime.Status;
@@ -232,9 +231,7 @@
 	}
 
 	public void throwParseErrorsException(Throwable e) throws CoreException {
-		Status status = new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.OK, "Error in the manifest file", //$NON-NLS-1$
-				e);
-		throw new CoreException(status);
+		throw new CoreException(Status.error("Error in the manifest file", e)); //$NON-NLS-1$
 	}
 
 	protected SAXParser getSaxParser() throws ParserConfigurationException, SAXException, FactoryConfigurationError {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BinaryRepositoryProvider.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BinaryRepositoryProvider.java
index 7dbe6c4..08b0e5f 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BinaryRepositoryProvider.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BinaryRepositoryProvider.java
@@ -115,7 +115,7 @@
 					return createProblemStatus();
 				}
 			}
-			return createOKStatus();
+			return Status.OK_STATUS;
 		}
 
 		@Override
@@ -123,7 +123,7 @@
 			if (isBinaryResource(file, false)) {
 				return createProblemStatus();
 			}
-			return createOKStatus();
+			return Status.OK_STATUS;
 		}
 	}
 
@@ -197,13 +197,7 @@
 	}
 
 	private IStatus createProblemStatus() {
-		String message = PDECoreMessages.BinaryRepositoryProvider_veto;
-		return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.OK, message, null);
-	}
-
-	private IStatus createOKStatus() {
-		return new Status(IStatus.OK, PDECore.PLUGIN_ID, IStatus.OK, "", //$NON-NLS-1$
-				null);
+		return Status.error(PDECoreMessages.BinaryRepositoryProvider_veto);
 	}
 
 	// we need to remove this but our tests will fail if we do, see bug 252003
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BundleManifestSourceLocationManager.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BundleManifestSourceLocationManager.java
index 1b6f0f2..8b4be03 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BundleManifestSourceLocationManager.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BundleManifestSourceLocationManager.java
@@ -21,7 +21,6 @@
 import java.util.Map;
 import java.util.Set;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.util.ManifestElement;
@@ -116,7 +115,7 @@
 						try {
 							version = new Version(versionEntry);
 						} catch (IllegalArgumentException e) {
-							PDECore.log(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(PDECoreMessages.SourceLocationManager_problemProcessingBundleManifestSourceHeader, pluginName, pluginVersion), e));
+							PDECore.log(Status.error(NLS.bind(PDECoreMessages.SourceLocationManager_problemProcessingBundleManifestSourceHeader, pluginName, pluginVersion), e));
 						}
 						if (pluginVersion.equals(version)) {
 							addSourceRoots(currentElement.getDirective("roots"), pluginSourceRoots); //$NON-NLS-1$
@@ -168,7 +167,7 @@
 					try {
 						return ManifestElement.parseHeader(ICoreConstants.ECLIPSE_SOURCE_BUNDLE, bundleSourceEntry);
 					} catch (BundleException e) {
-						PDECore.log(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(PDECoreMessages.SourceLocationManager_problemProcessingBundleManifestSourceHeader, pluginName, pluginVersion), e));
+						PDECore.log(Status.error(NLS.bind(PDECoreMessages.SourceLocationManager_problemProcessingBundleManifestSourceHeader, pluginName, pluginVersion), e));
 					}
 				}
 			}
@@ -206,7 +205,7 @@
 					try {
 						manifestElements = ManifestElement.parseHeader(ICoreConstants.ECLIPSE_SOURCE_BUNDLE, bundleSourceEntry);
 					} catch (BundleException e) {
-						PDECore.log(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(PDECoreMessages.SourceLocationManager_problemProcessingBundleManifestSourceHeader, currentPlugin.getId(), currentPlugin.getVersion()), e));
+						PDECore.log(Status.error(NLS.bind(PDECoreMessages.SourceLocationManager_problemProcessingBundleManifestSourceHeader, currentPlugin.getId(), currentPlugin.getVersion()), e));
 					}
 					if (manifestElements != null) {
 						IPath path = new Path(model.getInstallLocation());
@@ -220,12 +219,12 @@
 									try {
 										version = new Version(versionEntry);
 									} catch (IllegalArgumentException e) {
-										PDECore.log(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(PDECoreMessages.SourceLocationManager_problemProcessingBundleManifestSourceHeader, new Object[] {currentPlugin.getName(), versionEntry, path.toString()}), e));
+										PDECore.log(Status.error(NLS.bind(PDECoreMessages.SourceLocationManager_problemProcessingBundleManifestSourceHeader, new Object[] {currentPlugin.getName(), versionEntry, path.toString()}), e));
 
 									}
 									fPluginToSourceBundle.put(new SourceLocationKey(binaryPluginName, version), model);
 								} else {
-									PDECore.log(new Status(IStatus.WARNING, PDECore.PLUGIN_ID, NLS.bind(PDECoreMessages.BundleManifestSourceLocationManager_problemProcessBundleManifestHeaderAttributeMissing, currentPlugin.getName())));
+									PDECore.log(Status.warning(NLS.bind(PDECoreMessages.BundleManifestSourceLocationManager_problemProcessBundleManifestHeaderAttributeMissing, currentPlugin.getName())));
 								}
 							}
 						}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BundleValidationOperation.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BundleValidationOperation.java
index 26db7be..c87bf28 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BundleValidationOperation.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/BundleValidationOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2013 IBM Corporation and others.
+ * Copyright (c) 2007, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -21,7 +21,6 @@
 import org.eclipse.core.resources.IWorkspaceRunnable;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
@@ -36,15 +35,15 @@
 
 	private static StateObjectFactory FACTORY;
 
-	private final IPluginModelBase[] fModels;
+	private final Set<IPluginModelBase> fModels;
 	private final Dictionary<?, ?>[] fProperties;
 	private State fState;
 
-	public BundleValidationOperation(IPluginModelBase[] models) {
+	public BundleValidationOperation(Set<IPluginModelBase> models) {
 		this(models, new Dictionary[] {TargetPlatformHelper.getTargetEnvironment()});
 	}
 
-	public BundleValidationOperation(IPluginModelBase[] models, Dictionary<?, ?>[] properties) {
+	public BundleValidationOperation(Set<IPluginModelBase> models, Dictionary<?, ?>[] properties) {
 		fModels = models;
 		fProperties = properties;
 	}
@@ -54,7 +53,7 @@
 		if (FACTORY == null) {
 			FACTORY = Platform.getPlatformAdmin().getFactory();
 		}
-		SubMonitor subMonitor = SubMonitor.convert(monitor, fModels.length + 1);
+		SubMonitor subMonitor = SubMonitor.convert(monitor, fModels.size() + 1);
 		fState = FACTORY.createState(true);
 		for (IPluginModelBase fModel : fModels) {
 			BundleDescription bundle = fModel.getBundleDescription();
@@ -82,7 +81,7 @@
 					alreadyDuplicated.add(bundle.getSymbolicName());
 					MultiStatus status = new MultiStatus(PDECore.PLUGIN_ID, 0, NLS.bind(PDECoreMessages.BundleValidationOperation_multiple_singletons, new String[] {Integer.toString(dups.length), bundle.getSymbolicName()}), null);
 					for (BundleDescription dup : dups) {
-						status.add(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, dup.getLocation()));
+						status.add(Status.error(dup.getLocation()));
 					}
 					map.put(bundle, new Object[] {status});
 				}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java
index fe67cbf..db606bd 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2020 IBM Corporation and others.
+ * Copyright (c) 2005, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -21,6 +21,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.regex.Pattern;
+import java.util.stream.Stream;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
@@ -87,9 +88,7 @@
 	}
 
 	private static void addSourceAndLibraries(IProject project, IPluginModelBase model, IBuild build, boolean clear, Map<?, ?> sourceLibraryMap, ArrayList<IClasspathEntry> result) throws CoreException {
-		String testPluginPattern = PDECore.getDefault().getPreferencesManager().getString(ICoreConstants.TEST_PLUGIN_PATTERN);
-		boolean isTestPlugin = testPluginPattern != null && testPluginPattern.length() > 0
-				&& Pattern.compile(testPluginPattern).matcher(project.getName()).find();
+		boolean isTestPlugin = hasTestPluginName(project);
 		HashSet<IPath> paths = new HashSet<>();
 
 		// keep existing source folders
@@ -141,21 +140,52 @@
 		}
 	}
 
-	private static IClasspathEntry updateTestAttribute(boolean isTestPlugin, IClasspathEntry entry) {
+	public static boolean hasTestPluginName(IProject project) {
+		String pattern = PDECore.getDefault().getPreferencesManager().getString(ICoreConstants.TEST_PLUGIN_PATTERN);
+		return pattern != null && !pattern.isEmpty() && Pattern.compile(pattern).matcher(project.getName()).find();
+	}
+
+	/**
+	 * Returns true if the given project is a java project that has
+	 * {@code IClasspathEntry#CPE_SOURCE source classpath-entries} that are all
+	 * marked as {@code IClasspathAttribute#TEST test sources}.
+	 *
+	 * @param project
+	 * @return true if the given project is a test java project
+	 */
+	public static boolean hasTestOnlyClasspath(IProject project) {
+		IJavaProject javaProject = JavaCore.create(project);
+		if (javaProject == null) {
+			return false;
+		}
+		try {
+			boolean hasSources = false;
+			for (IClasspathEntry entry : javaProject.getRawClasspath()) {
+				if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+					hasSources = true;
+					if (!entry.isTest()) {
+						return false;
+					}
+				}
+			}
+			return hasSources; // if it has sources, all are test-sources
+		} catch (JavaModelException e) { // assume no valid java-project
+		}
+		return false;
+	}
+
+	public static IClasspathEntry updateTestAttribute(boolean isTestPlugin, IClasspathEntry entry) {
 		if (isTestPlugin == entry.isTest() || entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) {
 			return entry;
 		}
-		IClasspathAttribute[] classpathAttributes = Arrays.stream(entry.getExtraAttributes())
-				.filter(e -> !e.getName().equals(IClasspathAttribute.TEST)).toArray(IClasspathAttribute[]::new);
+		Stream<IClasspathAttribute> cpAttributes = Arrays.stream(entry.getExtraAttributes())
+				.filter(e -> !e.getName().equals(IClasspathAttribute.TEST));
 		if (isTestPlugin) {
-			int length = classpathAttributes.length;
-			System.arraycopy(classpathAttributes, 0, classpathAttributes = new IClasspathAttribute[length + 1], 0,
-					length);
-			classpathAttributes[length] = JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true"); //$NON-NLS-1$
+			IClasspathAttribute testAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true"); //$NON-NLS-1$
+			cpAttributes = Stream.concat(cpAttributes, Stream.of(testAttribute));
 		}
-		return JavaCore.newSourceEntry(entry.getPath(), entry.getInclusionPatterns(),
-				entry.getExclusionPatterns(), entry.getOutputLocation(), classpathAttributes);
-
+		return JavaCore.newSourceEntry(entry.getPath(), entry.getInclusionPatterns(), entry.getExclusionPatterns(),
+				entry.getOutputLocation(), cpAttributes.toArray(IClasspathAttribute[]::new));
 	}
 
 	private static IClasspathAttribute[] getClasspathAttributes(IProject project, IPluginModelBase model) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathHelper.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathHelper.java
index 16655d2..48176ab 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathHelper.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathHelper.java
@@ -12,6 +12,7 @@
  *     IBM Corporation - initial API and implementation
  *     Hannes Wellmann - Bug 577541 - Clean up ClasspathHelper and TargetWeaver
  *     Hannes Wellmann - Bug 577543 - Only weave dev.properties for secondary launches if plug-in is from Running-Platform
+ *     Hannes Wellmann - Bug 577118 - Handle multiple Plug-in versions in launching facility
  *******************************************************************************/
 package org.eclipse.pde.internal.core;
 
@@ -20,9 +21,9 @@
 import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -51,6 +52,7 @@
 import org.eclipse.pde.core.build.IBuild;
 import org.eclipse.pde.core.build.IBuildEntry;
 import org.eclipse.pde.core.plugin.IFragmentModel;
+import org.eclipse.pde.core.plugin.IPluginBase;
 import org.eclipse.pde.core.plugin.IPluginLibrary;
 import org.eclipse.pde.core.plugin.IPluginModelBase;
 import org.eclipse.pde.core.plugin.PluginRegistry;
@@ -64,19 +66,19 @@
 
 	private static final String DOT = "."; //$NON-NLS-1$
 	private static final String FRAGMENT_ANNOTATION = "@fragment@"; //$NON-NLS-1$
+	private static final String DEV_CLASSPATH_ENTRY_SEPARATOR = ","; //$NON-NLS-1$
+	private static final String DEV_CLASSPATH_VERSION_SEPARATOR = ";"; //$NON-NLS-1$
 
 	public static String getDevEntriesProperties(String fileName, boolean checkExcluded) throws CoreException {
 		IPluginModelBase[] models = PluginRegistry.getWorkspaceModels();
-		Map<String, IPluginModelBase> bundleModels = new HashMap<>();
-		for (IPluginModelBase model : models) {
-			bundleModels.put(model.getPluginBase().getId(), model);
-		}
+		Map<String, List<IPluginModelBase>> bundleModels = Arrays.stream(models)
+				.collect(Collectors.groupingBy(m -> m.getPluginBase().getId()));
 
 		Properties properties = getDevEntriesProperties(bundleModels, checkExcluded);
 		return writeDevEntries(fileName, properties);
 	}
 
-	public static String getDevEntriesProperties(String fileName, Map<String, IPluginModelBase> map)
+	public static String getDevEntriesProperties(String fileName, Map<String, List<IPluginModelBase>> map)
 			throws CoreException {
 		Properties properties = getDevEntriesProperties(map, true);
 		return writeDevEntries(fileName, properties);
@@ -99,26 +101,70 @@
 		}
 	}
 
-	public static Properties getDevEntriesProperties(Map<String, IPluginModelBase> bundlesMap, boolean checkExcluded) {
-		Properties properties = new Properties();
+	public static Properties getDevEntriesProperties(Map<String, List<IPluginModelBase>> bundlesMap,
+			boolean checkExcluded) {
+
+		Set<IPluginModelBase> launchedPlugins = bundlesMap.values().stream().flatMap(Collection::stream)
+				.collect(Collectors.toCollection(LinkedHashSet::new));
+
+		Map<IPluginModelBase, String> modelEntries = new LinkedHashMap<>();
 		// account for cascading workspaces
-		TargetWeaver.weaveRunningPlatformDevProperties(properties, bundlesMap.values());
-		for (IPluginModelBase model : bundlesMap.values()) {
-			if (model.getUnderlyingResource() != null) {
-				String entry = formatEntry(getDevPaths(model, checkExcluded, bundlesMap.keySet()));
-				if (!entry.isEmpty()) {
-					// overwrite entry, if plug-in from primary Eclipse is also
-					// imported into workspace of secondary eclipse
-					properties.put(model.getPluginBase().getId(), entry);
+		TargetWeaver.weaveRunningPlatformDevProperties(modelEntries, launchedPlugins);
+
+		for (List<IPluginModelBase> models : bundlesMap.values()) {
+			for (IPluginModelBase model : models) {
+				if (model.getUnderlyingResource() != null) {
+					String entry = formatEntry(getDevPaths(model, checkExcluded, launchedPlugins));
+					if (!entry.isEmpty()) {
+						// overwrite entry, if plug-in from primary Eclipse is
+						// also imported into workspace of secondary eclipse
+						modelEntries.put(model, entry);
+					}
+				}
+			}
+			// Check if there is an entry of a workspace-model or
+			// a target model woven from a primary-workspace plugin with same id
+			if (models.stream().anyMatch(modelEntries::containsKey)) {
+				for (IPluginModelBase model : models) {
+					// in case of multiple models with same id add empty entries
+					// for target-bundles to ensure the non-version entry is not
+					// used to falsely extend their class-path
+					modelEntries.putIfAbsent(model, ""); //$NON-NLS-1$
 				}
 			}
 		}
+
+		Properties properties = new Properties();
+		modelEntries.forEach((m, cp) -> addDevClasspath(m.getPluginBase(), properties, cp, false));
 		properties.put("@ignoredot@", "true"); //$NON-NLS-1$ //$NON-NLS-2$
 		return properties;
 	}
 
 	private static String formatEntry(Collection<IPath> paths) {
-		return paths.stream().map(IPath::toString).collect(Collectors.joining(",")); //$NON-NLS-1$
+		return paths.stream().map(IPath::toString).collect(Collectors.joining(DEV_CLASSPATH_ENTRY_SEPARATOR));
+	}
+
+	public static void addDevClasspath(IPluginBase model, Properties devProperties, String devCP, boolean append) {
+		// add entries with & without version to be backward-compatible with
+		// 'old' Equinox, that doesn't consider versions, too.
+		String id = model.getId();
+		if (!devCP.isEmpty()) {
+			addDevCPEntry(id, devCP, devProperties, append);
+		}
+		addDevCPEntry(id + DEV_CLASSPATH_VERSION_SEPARATOR + model.getVersion(), devCP, devProperties, append);
+	}
+
+	private static void addDevCPEntry(String id, String devCP, Properties devProperties, boolean append) {
+		if (append) {
+			devProperties.merge(id, devCP, (vOld, vNew) -> vOld + DEV_CLASSPATH_ENTRY_SEPARATOR + vNew);
+		} else {
+			devProperties.put(id, devCP);
+		}
+	}
+
+	public static String getDevClasspath(Properties devProperties, String id, String version) {
+		Object cp = devProperties.get(id + ClasspathHelper.DEV_CLASSPATH_VERSION_SEPARATOR + version);
+		return (String) (cp != null ? cp : devProperties.get(id)); // prefer version-entry
 	}
 
 	// creates a map whose key is a Path to the source directory/jar and the value is a Path output directory or jar.
@@ -213,7 +259,7 @@
 		return paths;
 	}
 
-	private static Set<IPath> getDevPaths(IPluginModelBase model, boolean checkExcluded, Set<String> plugins) {
+	private static Set<IPath> getDevPaths(IPluginModelBase model, boolean checkExcluded, Set<IPluginModelBase> plugins) {
 		IProject project = model.getUnderlyingResource().getProject();
 		try {
 			if (project.hasNature(JavaCore.NATURE_ID)) {
@@ -263,10 +309,10 @@
 	}
 
 	// looks for fragments for a plug-in.  Then searches the fragments for a specific library.  Will return paths which are absolute (required by runtime)
-	private static List<IPath> findLibraryFromFragments(String libName, IPluginModelBase model, boolean checkExcluded, Set<String> plugins) {
+	private static List<IPath> findLibraryFromFragments(String libName, IPluginModelBase model, boolean checkExcluded, Set<IPluginModelBase> plugins) {
 		IFragmentModel[] frags = PDEManager.findFragmentsFor(model);
 		for (int i = 0; i < frags.length; i++) {
-			if (!plugins.contains(frags[i].getBundleDescription().getSymbolicName())) {
+			if (!plugins.contains(frags[i])) {
 				continue;
 			}
 			// look in project first
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java
index e4a997f..7d9d63c 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2005, 2021 IBM Corporation and others.
+ *  Copyright (c) 2005, 2022 IBM Corporation and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -24,10 +24,12 @@
 import java.util.List;
 import java.util.Queue;
 import java.util.Set;
+import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.osgi.service.resolver.BundleDescription;
 import org.eclipse.osgi.service.resolver.State;
 import org.eclipse.pde.core.plugin.IPluginModelBase;
+import org.eclipse.pde.core.plugin.PluginRegistry;
 import org.eclipse.pde.core.target.ITargetPlatformService;
 import org.eclipse.pde.core.target.NameVersionDescriptor;
 import org.osgi.framework.Constants;
@@ -49,9 +51,26 @@
 	private DependencyManager() { // static use only
 	}
 
+	public enum Options {
+		/** Specifies to include all optional dependencies into the closure. */
+		INCLUDE_OPTIONAL_DEPENDENCIES,
+
+		/**
+		 * Specifies to include all fragments into the closure (must not be
+		 * combined with {@link #INCLUDE_NON_TEST_FRAGMENTS}).
+		 */
+		INCLUDE_ALL_FRAGMENTS,
+		/**
+		 * Specifies to include all non-test fragments into the closure (must
+		 * not be combined with {@link #INCLUDE_ALL_FRAGMENTS}).
+		 */
+		INCLUDE_NON_TEST_FRAGMENTS;
+	}
+
 	/**
 	 * Returns a {@link Set} of bundle descriptions of the given
-	 * {@link IPluginModelBase}s and all of their required dependencies.
+	 * {@link IPluginModelBase}s and all of their required dependencies
+	 * (including optional) and fragments.
 	 * <p>
 	 * The set includes the descriptions of the given model bases as well as all
 	 * transitively computed explicit, implicit (defined in the target-platform)
@@ -68,7 +87,7 @@
 	public static Set<BundleDescription> getSelfAndDependencies(Collection<IPluginModelBase> plugins) {
 		Collection<NameVersionDescriptor> implicit = getImplicitDependencies();
 		List<BundleDescription> bundles = mergeBundleDescriptions(plugins, implicit, TargetPlatformHelper.getState());
-		return findRequirementsClosure(bundles, true);
+		return findRequirementsClosure(bundles, Options.INCLUDE_OPTIONAL_DEPENDENCIES, Options.INCLUDE_ALL_FRAGMENTS);
 	}
 
 	/**
@@ -88,39 +107,37 @@
 	 *            {@link Set}
 	 * @param state
 	 *            the {@link State} to compute the dependencies in
-	 * @param includeOptional
-	 *            if optional bundle descriptions should be included
+	 * @param options
+	 *            the specified {@link Options} for computing the closure
 	 * @return a set of bundle descriptions
 	 */
 	public static Set<BundleDescription> getDependencies(Collection<IPluginModelBase> plugins,
-			Collection<NameVersionDescriptor> implicit, State state, boolean includeOptional) {
+			Collection<NameVersionDescriptor> implicit, State state, Options... options) {
 		List<BundleDescription> bundles = mergeBundleDescriptions(plugins, implicit, state);
-		Set<BundleDescription> closure = findRequirementsClosure(bundles, includeOptional);
+		Set<BundleDescription> closure = findRequirementsClosure(bundles, options);
 		plugins.forEach(p -> closure.remove(p.getBundleDescription()));
 		return closure;
 	}
 
 	/**
-	 *
 	 * Returns a {@link Set} of bundle descriptions for all required
 	 * dependencies of the given {@link IPluginModelBase}s.
 	 * <p>
 	 * The set includes the descriptions of the transitively computed explicit,
 	 * implicit (defined in the target-platform) and optional (if requested)
 	 * dependencies. The set does not include the descriptions of the given
-	 * objects but does include.
+	 * objects.
 	 * </p>
 	 *
 	 * @param plugins
 	 *            selected the group of {@link IPluginModelBase}s to compute
 	 *            dependencies for.
-	 * @param includeOptional
-	 *            if optional bundle descriptions should be included
+	 * @param options
+	 *            the specified {@link Options} for computing the closure
 	 * @return a set of bundle descriptions
 	 */
-	public static Set<BundleDescription> getDependencies(Collection<IPluginModelBase> plugins,
-			boolean includeOptional) {
-		return getDependencies(plugins, getImplicitDependencies(), TargetPlatformHelper.getState(), includeOptional);
+	public static Set<BundleDescription> getDependencies(Collection<IPluginModelBase> plugins, Options... options) {
+		return getDependencies(plugins, getImplicitDependencies(), TargetPlatformHelper.getState(), options);
 	}
 
 	/**
@@ -136,15 +153,23 @@
 	 * @param bundles
 	 *            the group of {@link BundleDescription}s to compute
 	 *            dependencies for.
-	 * @param includeOptional
-	 *            if optional bundle ids should be included
+	 * @param options
+	 *            the specified {@link Options} for computing the closure
 	 * @return a set of bundle descriptions
 	 */
 	public static Set<BundleDescription> findRequirementsClosure(Collection<BundleDescription> bundles,
-			boolean includeOptional) {
+			Options... options) {
+
+		Set<Options> optionSet = Set.of(options);
+		boolean includeOptional = optionSet.contains(Options.INCLUDE_OPTIONAL_DEPENDENCIES);
+		boolean includeAllFragments = optionSet.contains(Options.INCLUDE_ALL_FRAGMENTS);
+		boolean includeNonTestFragments = optionSet.contains(Options.INCLUDE_NON_TEST_FRAGMENTS);
+		if (includeAllFragments && includeNonTestFragments) {
+			throw new AssertionError("Cannot combine INCLUDE_ALL_FRAGMENTS and INCLUDE_NON_TEST_FRAGMENTS"); //$NON-NLS-1$
+		}
 
 		Set<BundleDescription> closure = new HashSet<>(bundles.size() * 4 / 3 + 1);
-		Queue<BundleDescription> pending = new ArrayDeque<>();
+		Queue<BundleDescription> pending = new ArrayDeque<>(bundles.size());
 
 		// initialize with given bundles
 		for (BundleDescription bundle : bundles) {
@@ -160,19 +185,29 @@
 				continue;
 			}
 
+			if (includeAllFragments || includeNonTestFragments) {
+				// A fragment's host is already required by a wire
+				for (BundleDescription fragment : bundle.getFragments()) {
+					if (includeAllFragments || !isTestWorkspaceProject(fragment)) {
+						addNewRequiredBundle(fragment, closure, pending);
+					}
+				}
+			}
+
 			List<BundleWire> requiredWires = wiring.getRequiredWires(null);
 			for (BundleWire wire : requiredWires) {
+				BundleRevision declaringBundle = wire.getRequirement().getRevision();
+				if (declaringBundle != bundle && !closure.contains(declaringBundle)) {
+					// Requirement is declared by an attached fragment, which is
+					// not included into the closure.
+					continue;
+				}
 				BundleRevision provider = getProvider(wire);
 				if (provider instanceof BundleDescription && (includeOptional || !isOptional(wire.getRequirement()))) {
 					BundleDescription requiredBundle = (BundleDescription) provider;
 					addNewRequiredBundle(requiredBundle, closure, pending);
 				}
 			}
-
-			// Add fragments. A fragment's host is already required by a wire
-			for (BundleDescription fragment : bundle.getFragments()) {
-				addNewRequiredBundle(fragment, closure, pending);
-			}
 		}
 		return closure;
 	}
@@ -195,6 +230,18 @@
 		return Constants.RESOLUTION_OPTIONAL.equals(requirement.getDirectives().get(Constants.RESOLUTION_DIRECTIVE));
 	}
 
+	private static boolean isTestWorkspaceProject(BundleDescription f) {
+		// Be defensive when declaring a fragment as 'test'-fragment
+		IPluginModelBase pluginModel = PluginRegistry.findModel(f);
+		if (pluginModel != null) {
+			IResource resource = pluginModel.getUnderlyingResource();
+			if (resource != null) {
+				return ClasspathComputer.hasTestOnlyClasspath(resource.getProject());
+			} // test-fragments are usually not part of the target-platform
+		}
+		return false;
+	}
+
 	/**
 	 * Computes the set of implicit dependencies from the
 	 * {@link PDEPreferencesManager}.
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ExternalFeatureModelManager.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ExternalFeatureModelManager.java
index 15c02bc..4ceaac1 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ExternalFeatureModelManager.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ExternalFeatureModelManager.java
@@ -27,7 +27,6 @@
 import java.util.Map;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.ListenerList;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubMonitor;
@@ -67,8 +66,7 @@
 			model.load(stream, false);
 			return model;
 		} catch (IOException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
-					NLS.bind(Messages.TargetFeature_FileDoesNotExist, manifest)));
+			throw new CoreException(Status.error(NLS.bind(Messages.TargetFeature_FileDoesNotExist, manifest)));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java
index a8db535..e28d771 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ICoreConstants.java
@@ -347,6 +347,11 @@
 	 */
 	String DISABLE_API_ANALYSIS_BUILDER = "Preferences.MainPage.disableAPIAnalysisBuilder";//$NON-NLS-1$
 	/**
+	 * Boolean preference whether API analysis should run asynchronous to the
+	 * build as background job
+	 */
+	String RUN_API_ANALYSIS_AS_JOB = "Preferences.MainPage.runAPIAnalysisAsJob";//$NON-NLS-1$
+	/**
 	 * Boolean preference whether add
 	 * '-Dorg.eclipse.swt.graphics.Resource.reportNonDisposed=true' to VM
 	 * arguments when creating a new launch configuration
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/MinimalState.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/MinimalState.java
index 73fa35a..37bb3dd 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/MinimalState.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/MinimalState.java
@@ -24,7 +24,6 @@
 import java.util.Map;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
@@ -184,7 +183,7 @@
 			// location causing the issue
 			MultiStatus status = new MultiStatus(PDECore.PLUGIN_ID, 0,
 					NLS.bind(UtilMessages.ErrorReadingManifest, bundleLocation.toString()), null);
-			status.add(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, e.getMessage()));
+			status.add(Status.error(e.getMessage()));
 			throw new CoreException(status);
 		} catch (IllegalArgumentException e) {
 		}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/P2Utils.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/P2Utils.java
index bd0be54..17e3330 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/P2Utils.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/P2Utils.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2018 IBM Corporation and others.
+ * Copyright (c) 2007, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -27,6 +27,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
+import java.util.stream.Collectors;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IStatus;
@@ -302,7 +303,8 @@
 						try {
 							level = Integer.parseInt(levelString);
 						} catch (NumberFormatException e) {
-							PDECore.log(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, "Error writing bundles, could not parse start level for bundle " + currentModel)); //$NON-NLS-1$
+							PDECore.log(Status.error(
+									"Error writing bundles, could not parse start level for bundle " + currentModel)); //$NON-NLS-1$
 						}
 					}
 					info.setMarkedAsStarted(isAuto);
@@ -310,7 +312,8 @@
 					bundleInfo.add(info);
 				}
 			} else {
-				PDECore.log(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, "Error writing bundles, could not find the bundle location for bundle " + currentModel)); //$NON-NLS-1$
+				PDECore.log(Status
+						.error("Error writing bundles, could not find the bundle location for bundle " + currentModel)); //$NON-NLS-1$
 			}
 		}
 
@@ -352,17 +355,17 @@
 	public static boolean profileExists(String profileID, File p2DataArea) throws CoreException {
 		IProvisioningAgentProvider provider = PDECore.getDefault().acquireService(IProvisioningAgentProvider.class);
 		if (provider == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.P2Utils_UnableToAcquireP2Service));
+			throw new CoreException(Status.error(PDECoreMessages.P2Utils_UnableToAcquireP2Service));
 		}
 
 		IProvisioningAgent agent = provider.createAgent(p2DataArea.toURI());
 		if (agent == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.P2Utils_UnableToAcquireP2Service));
+			throw new CoreException(Status.error(PDECoreMessages.P2Utils_UnableToAcquireP2Service));
 		}
 
 		IProfileRegistry registry = (IProfileRegistry) agent.getService(IProfileRegistry.SERVICE_NAME);
 		if (registry == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.P2Utils_UnableToAcquireP2Service));
+			throw new CoreException(Status.error(PDECoreMessages.P2Utils_UnableToAcquireP2Service));
 		}
 
 		return registry.containsProfile(profileID);
@@ -379,26 +382,26 @@
 	 *
 	 * @throws CoreException if the profile cannot be generated
 	 */
-	public static void createProfile(String profileID, File p2DataArea, Collection<?> bundles) throws CoreException {
+	public static void createProfile(String profileID, File p2DataArea, Collection<List<IPluginModelBase>> bundles) throws CoreException {
 		// Acquire the required p2 services, creating an agent in the target p2 metadata area
 		IProvisioningAgentProvider provider = PDECore.getDefault().acquireService(IProvisioningAgentProvider.class);
 		if (provider == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.P2Utils_UnableToAcquireP2Service));
+			throw new CoreException(Status.error(PDECoreMessages.P2Utils_UnableToAcquireP2Service));
 		}
 
 		IProvisioningAgent agent = provider.createAgent(p2DataArea.toURI());
 		if (agent == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.P2Utils_UnableToAcquireP2Service));
+			throw new CoreException(Status.error(PDECoreMessages.P2Utils_UnableToAcquireP2Service));
 		}
 
 		IProfileRegistry registry = (IProfileRegistry) agent.getService(IProfileRegistry.SERVICE_NAME);
 		if (registry == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.P2Utils_UnableToAcquireP2Service));
+			throw new CoreException(Status.error(PDECoreMessages.P2Utils_UnableToAcquireP2Service));
 		}
 
 		IEngine engine = (IEngine) agent.getService(IEngine.SERVICE_NAME);
 		if (engine == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.P2Utils_UnableToAcquireP2Service));
+			throw new CoreException(Status.error(PDECoreMessages.P2Utils_UnableToAcquireP2Service));
 		}
 
 		// Delete any previous profiles with the same ID
@@ -416,12 +419,9 @@
 		profile = registry.addProfile(profileID, props);
 
 		// Create metadata for the bundles
-		Collection<IInstallableUnit> ius = new ArrayList<>(bundles.size());
-		for (final Object name : bundles) {
-			IPluginModelBase model = (IPluginModelBase) name;
-			BundleDescription bundle = model.getBundleDescription();
-			ius.add(createBundleIU(bundle));
-		}
+		Collection<IInstallableUnit> ius = bundles.stream().flatMap(Collection::stream)
+				.map(IPluginModelBase::getBundleDescription).map(P2Utils::createBundleIU) //
+				.collect(Collectors.toList());
 
 		// Add the metadata to the profile
 		ProvisioningContext context = new ProvisioningContext(agent);
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDECore.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDECore.java
index 91dd37c..e288a1b 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDECore.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDECore.java
@@ -121,13 +121,13 @@
 		}
 		IStatus status = null;
 		if (e instanceof CoreException || e.getMessage() != null) {
-			status = new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, e.getMessage(), e);
+			status = Status.error(e.getMessage(), e);
 		}
 		log(status);
 	}
 
 	public static void logErrorMessage(String message) {
-		log(new Status(IStatus.ERROR, PLUGIN_ID, IStatus.ERROR, message, null));
+		log(Status.error(message));
 	}
 
 	public static void logException(Throwable e) {
@@ -140,7 +140,7 @@
 		}
 		IStatus status = null;
 		if (e instanceof CoreException) {
-			status = new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, message, e);
+			status = Status.error(message, e);
 		} else {
 			if (message == null) {
 				message = e.getMessage();
@@ -148,7 +148,7 @@
 			if (message == null) {
 				message = e.toString();
 			}
-			status = new Status(IStatus.ERROR, PLUGIN_ID, IStatus.OK, message, e);
+			status = Status.error(message, e);
 		}
 		log(status);
 	}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PluginModelManager.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PluginModelManager.java
index ac2041b..bf810b5 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PluginModelManager.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PluginModelManager.java
@@ -594,7 +594,7 @@
 			if (PDECore.DEBUG_MODEL) {
 				System.out.println("Target platform initialization cancelled by user"); //$NON-NLS-1$
 			}
-			PDECore.log(new Status(IStatus.WARNING, PDECore.PLUGIN_ID, PDECoreMessages.PluginModelManager_TargetInitCancelledLog));
+			PDECore.log(Status.warning(PDECoreMessages.PluginModelManager_TargetInitCancelledLog));
 			// Set a flag so the feature model manager can avoid starting the target resolve again
 			fCancelled = true;
 		}
@@ -679,7 +679,7 @@
 		// Log any known issues with the target platform to warn user
 		if (target.isResolved()) {
 			if (target.getStatus().getSeverity() == IStatus.ERROR) {
-				PDECore.log(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.PluginModelManager_CurrentTargetPlatformContainsErrors, new CoreException(target.getStatus())));
+				PDECore.log(Status.error(PDECoreMessages.PluginModelManager_CurrentTargetPlatformContainsErrors, new CoreException(target.getStatus())));
 				if (target.getStatus() instanceof MultiStatus) {
 					MultiStatus multiStatus = (MultiStatus) target.getStatus();
 					for (IStatus childStatus : multiStatus.getChildren()) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PreferenceInitializer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PreferenceInitializer.java
index 8f931fa..503a50d 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PreferenceInitializer.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PreferenceInitializer.java
@@ -75,6 +75,7 @@
 		PDEPreferencesManager corePrefs = PDECore.getDefault().getPreferencesManager();
 		corePrefs.setDefault(ICoreConstants.WORKSPACE_PLUGINS_OVERRIDE_TARGET, true);
 		corePrefs.setDefault(ICoreConstants.DISABLE_API_ANALYSIS_BUILDER, false);
+		corePrefs.setDefault(ICoreConstants.RUN_API_ANALYSIS_AS_JOB, true);
 		corePrefs.setDefault(ICoreConstants.ADD_SWT_NON_DISPOSAL_REPORTING, true);
 		corePrefs.setDefault(ICoreConstants.TEST_PLUGIN_PATTERN, ICoreConstants.TEST_PLUGIN_PATTERN_DEFAULTVALUE);
 	}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/TargetPlatformHelper.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/TargetPlatformHelper.java
index 2abd41f..e791225 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/TargetPlatformHelper.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/TargetPlatformHelper.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -26,7 +26,6 @@
 import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.Hashtable;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -40,7 +39,6 @@
 import org.eclipse.core.runtime.IExtension;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
@@ -235,13 +233,12 @@
 		return null;
 	}
 
-	public static void checkPluginPropertiesConsistency(Map<?, ?> map, File configDir) {
+	public static void checkPluginPropertiesConsistency(Map<String, List<IPluginModelBase>> map, File configDir) {
 		File runtimeDir = new File(configDir, IPDEBuildConstants.BUNDLE_CORE_RUNTIME);
 		if (runtimeDir.exists() && runtimeDir.isDirectory()) {
 			long timestamp = runtimeDir.lastModified();
-			Iterator<?> iter = map.values().iterator();
-			while (iter.hasNext()) {
-				if (hasChanged((IPluginModelBase) iter.next(), timestamp)) {
+			for (List<IPluginModelBase> models : map.values()) {
+				if (models.stream().anyMatch(m -> hasChanged(m, timestamp))) {
 					CoreUtility.deleteContent(runtimeDir);
 					break;
 				}
@@ -283,8 +280,7 @@
 	public static ITargetDefinition getUnresolvedRepositoryBasedWorkspaceTarget() throws CoreException {
 		ITargetPlatformService service = PDECore.getDefault().acquireService(ITargetPlatformService.class);
 		if (service == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
-					PDECoreMessages.TargetPlatformHelper_CouldNotAcquireTargetService));
+			throw new CoreException(Status.error(PDECoreMessages.TargetPlatformHelper_CouldNotAcquireTargetService));
 		}
 		final ITargetDefinition target = service.getWorkspaceTargetDefinition();
 		if (target != null && !target.isResolved()) {
@@ -489,7 +485,7 @@
 		}
 
 		String version = model.getPluginBase().getVersion();
-		if (VersionUtil.validateVersion(version).getSeverity() == IStatus.OK) {
+		if (VersionUtil.validateVersion(version).isOK()) {
 			Version vid = new Version(version);
 			int major = vid.getMajor();
 			int minor = vid.getMinor();
@@ -625,8 +621,7 @@
 	public static ITargetDefinition getWorkspaceTargetResolved(IProgressMonitor monitor) throws CoreException {
 		ITargetPlatformService service = PDECore.getDefault().acquireService(ITargetPlatformService.class);
 		if (service == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
-					PDECoreMessages.TargetPlatformHelper_CouldNotAcquireTargetService));
+			throw new CoreException(Status.error(PDECoreMessages.TargetPlatformHelper_CouldNotAcquireTargetService));
 		}
 		final ITargetDefinition target = service.getWorkspaceTargetDefinition();
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/TargetWeaver.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/TargetWeaver.java
index 1d6da8b..7650867 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/TargetWeaver.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/TargetWeaver.java
@@ -13,6 +13,7 @@
  *     EclipseSource Corporation - ongoing enhancements
  *     Hannes Wellmann - Bug 577541 - Clean up ClasspathHelper and TargetWeaver
  *     Hannes Wellmann - Bug 577543 - Only weave dev.properties for secondary launches if plug-in is from Running-Platform
+ *     Hannes Wellmann - Bug 577118 - Handle multiple Plug-in versions in launching facility
  *******************************************************************************/
 package org.eclipse.pde.internal.core;
 
@@ -126,14 +127,14 @@
 	 * @param launchDevProperties dev.properties
 	 * @param launchedPlugins the bundles that participate in secondary runtime
 	 */
-	static void weaveRunningPlatformDevProperties(Properties launchDevProperties,
+	static void weaveRunningPlatformDevProperties(Map<IPluginModelBase, String> launchDevProperties,
 			Iterable<IPluginModelBase> launchedPlugins) {
 		if (fgDevPropertiesURL != null) {
 			Properties platformDevProperties = getDevProperties();
 			for (IPluginModelBase launchedPlugin : launchedPlugins) {
 				String devCP = getDevProperty(launchedPlugin, platformDevProperties);
 				if (devCP != null) {
-					launchDevProperties.setProperty(launchedPlugin.getPluginBase().getId(), devCP);
+					launchDevProperties.put(launchedPlugin, devCP);
 				}
 			}
 		}
@@ -186,7 +187,7 @@
 	}
 
 	private static String getDevProperty(Path bundleLocation, String id, String version, Properties devProperties) {
-		String devCP = (String) devProperties.get(id);
+		String devCP = ClasspathHelper.getDevClasspath(devProperties, id, version);
 		return devCP != null && isBundleOfRunningPlatform(bundleLocation, id, version) ? devCP : null;
 	}
 
@@ -209,6 +210,9 @@
 		// range like: "[" + version + "," + version + "]"
 		Version version = Version.parseVersion(versionStr);
 		Bundle[] platformBundles = Platform.getBundles(symbolicName, null);
+		if (platformBundles == null) {
+			return null;
+		}
 		return Arrays.stream(platformBundles).filter(b -> b.getVersion().equals(version)).findAny().orElse(null);
 	}
 }
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/UpdateManagerHelperDeprecated.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/UpdateManagerHelperDeprecated.java
index fde742e..a5c5c68 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/UpdateManagerHelperDeprecated.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/UpdateManagerHelperDeprecated.java
@@ -30,7 +30,6 @@
 import javax.xml.parsers.ParserConfigurationException;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.core.plugin.IPluginModelBase;
@@ -139,7 +138,7 @@
 			if (message == null || message.length() == 0) {
 				message = PDECoreMessages.TargetPlatform_exceptionThrown;
 			}
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.ERROR, message, e));
+			throw new CoreException(Status.error(message, e));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/build/BuildObject.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/build/BuildObject.java
index 3f49483..73b9f9a 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/build/BuildObject.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/build/BuildObject.java
@@ -14,10 +14,8 @@
 package org.eclipse.pde.internal.core.build;
 
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.core.build.IBuildModel;
-import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.PDECoreMessages;
 
 public class BuildObject implements IBuildObject {
@@ -50,8 +48,7 @@
 	}
 
 	protected void throwCoreException(String message) throws CoreException {
-		Status status = new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.OK, message, null);
-		throw new CoreException(status);
+		throw new CoreException(Status.error(message));
 	}
 
 	/**
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/BuildErrorReporter.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/BuildErrorReporter.java
index 22ea882..36bed76 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/BuildErrorReporter.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/BuildErrorReporter.java
@@ -318,7 +318,7 @@
 		}
 
 		validateMissingSourceInBinIncludes(binIncludes, sourceEntryKeys, build);
-		validateBinIncludes(binIncludes);
+		validateBinIncludes(binIncludes, outputEntries);
 		validateExecutionEnvironment(javacSource, javacTarget, jreCompilationProfile, javacWarnings, javacErrors, getSourceLibraries(sourceEntries));
 		validateJavaCompilerSettings(javaProjectWarnings);
 	}
@@ -613,7 +613,7 @@
 		return compliance;
 	}
 
-	private void validateBinIncludes(IBuildEntry binIncludes) {
+	private void validateBinIncludes(IBuildEntry binIncludes, List<IBuildEntry> outputEntries) {
 		// make sure we have a manifest entry
 		if (PDEProject.getManifest(fProject).exists()) {
 			validateBinIncludes(binIncludes, ICoreConstants.MANIFEST_FOLDER_NAME);
@@ -646,6 +646,12 @@
 
 		}
 
+		// make sure "." library is included if present
+		String defaultLibraryName = PROPERTY_OUTPUT_PREFIX + "."; //$NON-NLS-1$
+		if (outputEntries.stream().anyMatch(e -> e.getName().equals(defaultLibraryName))) {
+			validateBinIncludes(binIncludes, "."); //$NON-NLS-1$
+		}
+
 		// validate for bundle localization
 		IPluginModelBase model = PluginRegistry.findModel(fProject);
 		if (model == null) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/ManifestErrorReporter.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/ManifestErrorReporter.java
index 8151dc6..1da45e0 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/ManifestErrorReporter.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/builders/ManifestErrorReporter.java
@@ -77,7 +77,7 @@
 
 	protected void validateVersionAttribute(Element element, Attr attr) {
 		IStatus status = VersionUtil.validateVersion(attr.getValue());
-		if (status.getSeverity() != IStatus.OK) {
+		if (!status.isOK()) {
 			report(status.getMessage(), getLine(element, attr.getName()), CompilerFlags.ERROR, PDEMarkerFactory.CAT_FATAL);
 		}
 	}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundleObject.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundleObject.java
index 98a3b63..8b518f6 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundleObject.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundleObject.java
@@ -16,13 +16,11 @@
 import java.io.PrintWriter;
 import java.io.Serializable;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.core.IModelChangeProvider;
 import org.eclipse.pde.core.IModelChangedEvent;
 import org.eclipse.pde.core.IWritable;
 import org.eclipse.pde.core.ModelChangedEvent;
-import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.ibundle.IBundleModel;
 import org.eclipse.pde.internal.core.plugin.IWritableDelimiter;
 
@@ -43,8 +41,7 @@
 	}
 
 	protected void throwCoreException(String message) throws CoreException {
-		Status status = new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.OK, message, null);
-		throw new CoreException(status);
+		throw new CoreException(Status.error(message));
 	}
 
 	protected void fireStructureChanged(BundleObject[] children, int changeType) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundlePluginBase.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundlePluginBase.java
index 0ef2e9e..1cd4f66 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundlePluginBase.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bundle/BundlePluginBase.java
@@ -17,7 +17,6 @@
 import java.io.Serializable;
 import java.util.ArrayList;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.PlatformObject;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.service.resolver.BundleDescription;
@@ -37,7 +36,6 @@
 import org.eclipse.pde.core.plugin.ISharedExtensionsModel;
 import org.eclipse.pde.core.plugin.ISharedPluginModel;
 import org.eclipse.pde.internal.core.ICoreConstants;
-import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.PDECoreMessages;
 import org.eclipse.pde.internal.core.TargetPlatformHelper;
 import org.eclipse.pde.internal.core.ibundle.IBundle;
@@ -938,10 +936,7 @@
 	}
 
 	protected void throwCoreException(String message) throws CoreException {
-		Status status = new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.OK, message, null);
-		CoreException ce = new CoreException(status);
-		ce.fillInStackTrace();
-		throw ce;
+		throw new CoreException(Status.error(message));
 	}
 
 }
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/ExecutionEnvironmentProfileManager.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/ExecutionEnvironmentProfileManager.java
index 8119fe0..7b02ae0 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/ExecutionEnvironmentProfileManager.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/ExecutionEnvironmentProfileManager.java
@@ -22,7 +22,6 @@
 import java.net.URL;
 import java.util.Properties;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.launching.JavaRuntime;
@@ -102,7 +101,7 @@
 							fgCustomCount++;
 							properties.store(stream, null);
 						} catch (IOException e) {
-							PDECore.log(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(PDECoreMessages.ExecutionEnvironmentProfileManager_0, env.getId()), e));
+							PDECore.log(Status.error(NLS.bind(PDECoreMessages.ExecutionEnvironmentProfileManager_0, env.getId()), e));
 						}
 					}
 				}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/FeatureBasedExportOperation.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/FeatureBasedExportOperation.java
index 169c908..e5fba78 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/FeatureBasedExportOperation.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/FeatureBasedExportOperation.java
@@ -65,11 +65,11 @@
 			}
 			return status;
 		} catch (IOException e) {
-			return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.FeatureBasedExportOperation_ProblemDuringExport, e);
+			return Status.error(PDECoreMessages.FeatureBasedExportOperation_ProblemDuringExport, e);
 		} catch (CoreException e) {
 			return e.getStatus();
 		} catch (InvocationTargetException e) {
-			return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.FeatureBasedExportOperation_ProblemDuringExport, e.getTargetException());
+			return Status.error(PDECoreMessages.FeatureBasedExportOperation_ProblemDuringExport, e.getTargetException());
 		} finally {
 			for (Object item : fInfo.items) {
 				if (item instanceof IModel) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/FeatureExportOperation.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/FeatureExportOperation.java
index 930fbaa..e7b2e19 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/FeatureExportOperation.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/FeatureExportOperation.java
@@ -176,9 +176,9 @@
 			}
 			return status;
 		} catch (InvocationTargetException | CoreException e) {
-			return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.FeatureBasedExportOperation_ProblemDuringExport, e.getCause() != null ? e.getCause() : e);
+			return Status.error(PDECoreMessages.FeatureBasedExportOperation_ProblemDuringExport, e.getCause() != null ? e.getCause() : e);
 		} catch (IOException e) {
-			return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.FeatureBasedExportOperation_ProblemDuringExport, e);
+			return Status.error(PDECoreMessages.FeatureBasedExportOperation_ProblemDuringExport, e);
 		}
 
 	}
@@ -1231,7 +1231,7 @@
 			Set<?> errors = getWorkspaceExportHelper().checkForErrors(fInfo.items);
 			subMonitor.split(5);
 			if (!errors.isEmpty()) {
-				return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(PDECoreMessages.FeatureExportOperation_workspaceBuildErrorsFoundDuringExport, errors.toString()));
+				return Status.error(NLS.bind(PDECoreMessages.FeatureExportOperation_workspaceBuildErrorsFoundDuringExport, errors));
 			}
 		}
 		return Status.OK_STATUS;
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/ProductExportOperation.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/ProductExportOperation.java
index b81ec9c..d8193ef 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/ProductExportOperation.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/exports/ProductExportOperation.java
@@ -95,7 +95,7 @@
 			} else if (line.startsWith(STATUS_SUBENTRY) && tokenizer.hasMoreElements() && status != null) {
 				String next = tokenizer.nextToken();
 				if (next.startsWith(STATUS_MESSAGE)) {
-					status.add(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, next.substring(8)));
+					status.add(Status.error(next.substring(8)));
 				}
 			}
 		}
@@ -135,7 +135,7 @@
 		} catch (IOException e) {
 			PDECore.log(e);
 		} catch (InvocationTargetException e) {
-			return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.FeatureBasedExportOperation_ProblemDuringExport, e.getTargetException());
+			return Status.error(PDECoreMessages.FeatureBasedExportOperation_ProblemDuringExport, e.getTargetException());
 		} catch (CoreException e) {
 			if (errorMessage != null) {
 				return parseErrorMessage(e);
@@ -154,7 +154,7 @@
 		}
 
 		if (hasAntErrors()) {
-			return new Status(IStatus.WARNING, PDECore.PLUGIN_ID, NLS.bind(PDECoreMessages.FeatureExportOperation_CompilationErrors, fInfo.destinationDirectory));
+			return Status.warning(NLS.bind(PDECoreMessages.FeatureExportOperation_CompilationErrors, fInfo.destinationDirectory));
 		}
 
 		errorMessage = null;
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureChild.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureChild.java
index a5cf964..b9ed2cb 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureChild.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureChild.java
@@ -28,7 +28,6 @@
 	private String fName;
 	private boolean fOptional;
 	private int fSearchLocation = ROOT;
-	private int fMatch = NONE;
 	private String fOs;
 	private String fWs;
 	private String fArch;
@@ -42,7 +41,6 @@
 		fOptional = false;
 		fName = null;
 		fSearchLocation = ROOT;
-		fMatch = NONE;
 		fOs = null;
 		fWs = null;
 		fArch = null;
@@ -61,15 +59,6 @@
 		fArch = getNodeAttribute(node, "arch"); //$NON-NLS-1$
 		fNl = getNodeAttribute(node, "nl"); //$NON-NLS-1$
 		fFilter = getNodeAttribute(node, "filter"); //$NON-NLS-1$
-		String matchName = getNodeAttribute(node, "match"); //$NON-NLS-1$
-		if (matchName != null) {
-			for (int i = 0; i < RULE_NAME_TABLE.length; i++) {
-				if (matchName.equals(RULE_NAME_TABLE[i])) {
-					fMatch = i;
-					break;
-				}
-			}
-		}
 		String searchLocationName = getNodeAttribute(node, "search_location"); //$NON-NLS-1$
 		if (searchLocationName == null) {
 			searchLocationName = getNodeAttribute(node, "search-location"); //$NON-NLS-1$
@@ -123,11 +112,6 @@
 	}
 
 	@Override
-	public int getMatch() {
-		return fMatch;
-	}
-
-	@Override
 	public String getOS() {
 		return fOs;
 	}
@@ -180,14 +164,6 @@
 	}
 
 	@Override
-	public void setMatch(int match) throws CoreException {
-		ensureModelEditable();
-		Integer oldValue = Integer.valueOf(this.fMatch);
-		this.fMatch = match;
-		firePropertyChanged(P_MATCH, oldValue, Integer.valueOf(match));
-	}
-
-	@Override
 	public void setSearchLocation(int searchLocation) throws CoreException {
 		ensureModelEditable();
 		Integer oldValue = Integer.valueOf(this.fSearchLocation);
@@ -255,9 +231,6 @@
 		case P_NAME:
 			setName((String) newValue);
 			break;
-		case P_MATCH:
-			setMatch(newValue != null ? ((Integer) newValue).intValue() : NONE);
-			break;
 		case P_OS:
 			setOS((String) newValue);
 			break;
@@ -307,10 +280,6 @@
 			writer.println();
 			writer.print(indent2 + "optional=\"true\""); //$NON-NLS-1$
 		}
-		if (fMatch != NONE) {
-			writer.println();
-			writer.print(indent2 + "match=\"" + RULE_NAME_TABLE[fMatch] + "\""); //$NON-NLS-1$ //$NON-NLS-2$
-		}
 		if (getOS() != null) {
 			writer.println();
 			writer.print(indent2 + "os=\"" + getOS() + "\""); //$NON-NLS-1$ //$NON-NLS-2$
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureImport.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureImport.java
index 6904769..22c1f14 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureImport.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureImport.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -29,17 +29,13 @@
 public class FeatureImport extends VersionableObject implements IFeatureImport {
 	private static final long serialVersionUID = 1L;
 	private int fMatch = NONE;
-	private int fIdMatch = PERFECT;
 	private int fType = PLUGIN;
 	private boolean fPatch = false;
 	private String fFilter = null;
 
-	public FeatureImport() {
-	}
-
 	public IPlugin getPlugin() {
 		if (id != null && fType == PLUGIN) {
-			IPluginModelBase model = PluginRegistry.findModel(id);
+			IPluginModelBase model = PluginRegistry.findModel(id, version, fMatch, null);
 			return model instanceof IPluginModel ? ((IPluginModel) model).getPlugin() : null;
 		}
 		return null;
@@ -80,17 +76,11 @@
 	}
 
 	@Override
-	public int getIdMatch() {
-		return fIdMatch;
-	}
-
-	@Override
 	protected void reset() {
 		super.reset();
 		fPatch = false;
 		fType = PLUGIN;
 		fMatch = NONE;
-		fIdMatch = PERFECT;
 		fFilter = null;
 	}
 
@@ -116,12 +106,6 @@
 				}
 			}
 		}
-		mvalue = getNodeAttribute(node, "id-match"); //$NON-NLS-1$
-		if (mvalue != null && mvalue.length() > 0) {
-			if (mvalue.equalsIgnoreCase(RULE_PREFIX)) {
-				fIdMatch = PREFIX;
-			}
-		}
 		fPatch = getBooleanAttribute(node, "patch"); //$NON-NLS-1$
 		fFilter = getNodeAttribute(node, "filter"); //$NON-NLS-1$
 	}
@@ -147,14 +131,6 @@
 	}
 
 	@Override
-	public void setIdMatch(int idMatch) throws CoreException {
-		ensureModelEditable();
-		Integer oldValue = Integer.valueOf(this.fIdMatch);
-		this.fIdMatch = idMatch;
-		firePropertyChanged(P_ID_MATCH, oldValue, Integer.valueOf(idMatch));
-	}
-
-	@Override
 	public int getType() {
 		return fType;
 	}
@@ -186,9 +162,6 @@
 		case P_MATCH:
 			setMatch(newValue != null ? ((Integer) newValue).intValue() : 0);
 			break;
-		case P_ID_MATCH:
-			setIdMatch(newValue != null ? ((Integer) newValue).intValue() : 0);
-			break;
 		case P_TYPE:
 			setType(newValue != null ? ((Integer) newValue).intValue() : PLUGIN);
 			break;
@@ -212,9 +185,6 @@
 		if (!fPatch && fMatch != NONE) {
 			writer.print(" match=\"" + RULE_NAME_TABLE[fMatch] + "\""); //$NON-NLS-1$ //$NON-NLS-2$
 		}
-		if (fIdMatch == PREFIX) {
-			writer.print(" id-match=\"prefix\""); //$NON-NLS-1$
-		}
 		if (fPatch) {
 			writer.print(" patch=\"true\""); //$NON-NLS-1$
 		}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureObject.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureObject.java
index 5c89c1e..41d6fe0 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureObject.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/feature/FeatureObject.java
@@ -15,12 +15,10 @@
 
 import java.io.PrintWriter;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.PlatformObject;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.core.IModelChangeProvider;
 import org.eclipse.pde.core.ModelChangedEvent;
-import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.PDECoreMessages;
 import org.eclipse.pde.internal.core.ifeature.IFeature;
 import org.eclipse.pde.internal.core.ifeature.IFeatureModel;
@@ -156,10 +154,7 @@
 	}
 
 	protected void throwCoreException(String message) throws CoreException {
-		Status status = new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.OK, message, null);
-		CoreException ce = new CoreException(status);
-		ce.fillInStackTrace();
-		throw ce;
+		throw new CoreException(Status.error(message));
 	}
 
 	public void restoreProperty(String name, Object oldValue, Object newValue) throws CoreException {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/EnvironmentHelper.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/EnvironmentHelper.java
new file mode 100644
index 0000000..dc1ecd4
--- /dev/null
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/EnvironmentHelper.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ *  Copyright (c) 2021, 2022 Hannes Wellmann and others.
+ *
+ *  This program and the accompanying materials
+ *  are made available under the terms of the Eclipse Public License 2.0
+ *  which accompanies this distribution, and is available at
+ *  https://www.eclipse.org/legal/epl-2.0/
+ *
+ *  SPDX-License-Identifier: EPL-2.0
+ *
+ *  Contributors:
+ *     Hannes Wellmann - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.core.ifeature;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.pde.core.target.ITargetDefinition;
+
+class EnvironmentHelper {
+	private EnvironmentHelper() {
+	}
+
+	/** @see IEnvironment#matchesEnvironment(ITargetDefinition) */
+	static boolean matchesTargetEnvironment(IEnvironment environment, ITargetDefinition target) {
+		return matchesProperty(environment.getOS(), target, ITargetDefinition::getOS, Platform::getOS)
+				&& matchesProperty(environment.getWS(), target, ITargetDefinition::getWS, Platform::getWS)
+				&& matchesProperty(environment.getArch(), target, ITargetDefinition::getArch, Platform::getOSArch)
+				&& matchesProperty(environment.getNL(), target, ITargetDefinition::getNL, Platform::getNL);
+	}
+
+	private static final Pattern ENVIRONMENT_FILTER_ELEMENT_SEPARATOR = Pattern.compile(","); //$NON-NLS-1$
+
+	private static boolean matchesProperty(String filter, ITargetDefinition target,
+			Function<ITargetDefinition, String> targetGetter, Supplier<String> defaultValue) {
+		if (filter == null) {
+			return true;
+		}
+		String targetEnvironment = targetGetter.apply(target);
+		if (targetEnvironment == null) {
+			targetEnvironment = defaultValue.get();
+		}
+		return ENVIRONMENT_FILTER_ELEMENT_SEPARATOR.splitAsStream(filter).map(String::strip)
+				.anyMatch(targetEnvironment::equals);
+	}
+}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IEnvironment.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IEnvironment.java
index 938b05c..c6a25c5 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IEnvironment.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IEnvironment.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2000, 2008 IBM Corporation and others.
+ *  Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -10,30 +10,86 @@
  *
  *  Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Hannes Wellmann - Bug 576890: Ignore included features/plug-ins not matching target-environment
  *******************************************************************************/
 package org.eclipse.pde.internal.core.ifeature;
 
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.pde.core.target.ITargetDefinition;
 
 public interface IEnvironment {
-	static final String P_OS = "os"; //$NON-NLS-1$
-	static final String P_WS = "ws"; //$NON-NLS-1$
-	static final String P_ARCH = "arch"; //$NON-NLS-1$
-	static final String P_NL = "nl"; //$NON-NLS-1$
+	String P_OS = "os"; //$NON-NLS-1$
+	String P_WS = "ws"; //$NON-NLS-1$
+	String P_ARCH = "arch"; //$NON-NLS-1$
+	String P_NL = "nl"; //$NON-NLS-1$
 
+	/**
+	 * Returns a comma-separated list of the operating systems this plug-in
+	 * supports.
+	 */
 	String getOS();
 
+	/**
+	 * Returns a comma-separated list of the window systems this plug-in
+	 * supports.
+	 */
 	String getWS();
 
+	/**
+	 * Returns a comma-separated list of the architecture this plug-in supports.
+	 */
 	String getArch();
 
+	/**
+	 * Returns a comma-separated list of the locales this plug-in supports.
+	 */
 	String getNL();
 
+	/**
+	 * Sets a comma-separated list of the operating systems this plug-in
+	 * supports.
+	 */
 	void setOS(String os) throws CoreException;
 
+	/**
+	 * Sets a comma-separated list of the window systems this plug-in supports.
+	 */
 	void setWS(String ws) throws CoreException;
 
+	/**
+	 * Sets a comma-separated list of the archiecture this plug-in supports.
+	 */
 	void setArch(String arch) throws CoreException;
 
+	/**
+	 * Sets a comma-separated list of the locales this plug-in supports.
+	 */
 	void setNL(String nl) throws CoreException;
+
+	/**
+	 * Returns true if this environment matches the given
+	 * {@link ITargetDefinition target-definition}.
+	 * <p>
+	 * A environment matches a certain property of the given target if its
+	 * corresponding property is either {@code null} or any element of its comma
+	 * separated list of values is {@link Object#equals(Object) equals} to the
+	 * target's value. If the target's value for a property is {@code null} this
+	 * environment's property is instead compared to the corresponding property
+	 * of the running {@link Platform} (e.g. {@link Platform#getOS()}.
+	 * </p>
+	 * <p>
+	 * This environment fully matches the target only if all properties of this
+	 * environment match each corresponding target property (or the one of the
+	 * running platform).
+	 * </p>
+	 *
+	 * @param target
+	 *            the target-definition to test
+	 * @return true if each property of this environment matches the
+	 *         corresponding target (or running platform) property.
+	 */
+	default boolean matchesEnvironment(ITargetDefinition target) {
+		return EnvironmentHelper.matchesTargetEnvironment(this, target);
+	}
 }
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureChild.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureChild.java
index bd19a3f..bbbd421 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureChild.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureChild.java
@@ -15,16 +15,14 @@
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.pde.core.IIdentifiable;
-import org.eclipse.pde.core.plugin.IMatchRules;
 
 /**
  * The reference to a plug-in that is part of this feature.
  */
-public interface IFeatureChild extends IFeatureObject, IIdentifiable, IMatchRules, IEnvironment {
+public interface IFeatureChild extends IFeatureObject, IIdentifiable, IEnvironment {
 	String P_VERSION = "version"; //$NON-NLS-1$
 	String P_OPTIONAL = "optional"; //$NON-NLS-1$
 	String P_NAME = "name"; //$NON-NLS-1$
-	String P_MATCH = "match"; //$NON-NLS-1$
 	String P_FILTER = "filter"; //$NON-NLS-1$
 	String P_SEARCH_LOCATION = "search-location"; //$NON-NLS-1$
 
@@ -48,10 +46,6 @@
 
 	void setSearchLocation(int location) throws CoreException;
 
-	int getMatch();
-
-	void setMatch(int match) throws CoreException;
-
 	String getFilter();
 
 	void setFilter(String filter) throws CoreException;
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureEntry.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureEntry.java
index 525b9ad..0afc0d6 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureEntry.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureEntry.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Hannes Wellmann - Bug 576890: Ignore included features/plug-ins not matching target-environment
  *******************************************************************************/
 package org.eclipse.pde.internal.core.ifeature;
 
@@ -19,7 +20,7 @@
 /**
  * The reference to a plug-in that is part of this feature.
  */
-public interface IFeatureEntry extends IFeatureObject, IIdentifiable {
+public interface IFeatureEntry extends IFeatureObject, IIdentifiable, IEnvironment {
 	String P_OS = "p_os"; //$NON-NLS-1$
 	String P_WS = "p_ws"; //$NON-NLS-1$
 	String P_NL = "p_nl"; //$NON-NLS-1$
@@ -29,72 +30,32 @@
 	String P_INSTALL_SIZE = "p_install_size"; //$NON-NLS-1$
 
 	/**
-	 * Returns a comma-separated list of the operating systems this plug-in supports.
-	 */
-	public String getOS();
-
-	/**
-	 * Returns a comma-separated list of the window systems this plug-in supports.
-	 */
-	public String getWS();
-
-	/**
-	 * Returns a comma-separated list of the locales this plug-in supports.
-	 */
-	public String getNL();
-
-	/**
-	 * Returns a comma-separated list of the architecture this plug-in supports.
-	 */
-	public String getArch();
-
-	/**
 	 * Returns an LDAP filter that must be satisfied for this entry
 	 */
-	public String getFilter();
+	String getFilter();
 
 	/**
 	 * 	Returns estimated download size of this plug-in.
 	 */
-	public long getDownloadSize();
+	long getDownloadSize();
 
 	/**
 	 * Returns estimated size of this plug-in when installed.
 	 */
-	public long getInstallSize();
-
-	/**
-	 * Sets a comma-separated list of the operating systems this plug-in supports.
-	 */
-	public void setOS(String os) throws CoreException;
-
-	/**
-	 * Sets a comma-separated list of the window systems this plug-in supports.
-	 */
-	public void setWS(String ws) throws CoreException;
-
-	/**
-	 * Sets a comma-separated list of the locales this plug-in supports.
-	 */
-	public void setNL(String nl) throws CoreException;
-
-	/**
-	 * Sets a comma-separated list of the archiecture this plug-in supports.
-	 */
-	public void setArch(String arch) throws CoreException;
+	long getInstallSize();
 
 	/**
 	 * Sets an LDAP filter on this plugin
 	 */
-	public void setFilter(String filter) throws CoreException;
+	void setFilter(String filter) throws CoreException;
 
 	/**
 	 * 	Sets the estimated download size of this plug-in.
 	 */
-	public void setDownloadSize(long size) throws CoreException;
+	void setDownloadSize(long size) throws CoreException;
 
 	/**
 	 * Sets the estimated size of this plug-in when installed.
 	 */
-	public void setInstallSize(long size) throws CoreException;
+	void setInstallSize(long size) throws CoreException;
 }
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureImport.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureImport.java
index d94ba0d..406f9e8 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureImport.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ifeature/IFeatureImport.java
@@ -21,8 +21,6 @@
 
 	String P_PATCH = "patch"; //$NON-NLS-1$
 
-	String P_ID_MATCH = "id-match"; //$NON-NLS-1$
-
 	int PLUGIN = 0;
 
 	int FEATURE = 1;
@@ -35,10 +33,6 @@
 
 	void setPatch(boolean patch) throws CoreException;
 
-	int getIdMatch();
-
-	void setIdMatch(int idMatch) throws CoreException;
-
 	String getFilter();
 
 	void setFilter(String filter) throws CoreException;
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/plugin/PluginObject.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/plugin/PluginObject.java
index 28c7789..ae9f604 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/plugin/PluginObject.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/plugin/PluginObject.java
@@ -17,7 +17,6 @@
 import java.io.Serializable;
 import java.util.Vector;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.PlatformObject;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.core.IModel;
@@ -29,7 +28,6 @@
 import org.eclipse.pde.core.plugin.IPluginModelBase;
 import org.eclipse.pde.core.plugin.IPluginObject;
 import org.eclipse.pde.core.plugin.ISharedPluginModel;
-import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.PDECoreMessages;
 import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelProvider;
 import org.eclipse.pde.internal.core.util.PDEXMLHelper;
@@ -194,10 +192,7 @@
 	}
 
 	protected void throwCoreException(String message) throws CoreException {
-		Status status = new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.OK, message, null);
-		CoreException ce = new CoreException(status);
-		ce.fillInStackTrace();
-		throw ce;
+		throw new CoreException(Status.error(message));
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/BundleProjectDescription.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/BundleProjectDescription.java
index 4b6622a..54dbe15 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/BundleProjectDescription.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/BundleProjectDescription.java
@@ -27,7 +27,6 @@
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
@@ -169,7 +168,7 @@
 				try {
 					return ManifestElement.parseHeader(key, value);
 				} catch (BundleException e) {
-					throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, e.getMessage(), e));
+					throw new CoreException(Status.error(e.getMessage(), e));
 				}
 			}
 			// empty header
@@ -208,7 +207,7 @@
 				headers = ManifestElement.parseBundleManifest(manifest.getContents(), null);
 				fReadHeaders = headers;
 			} catch (IOException | BundleException e) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, e.getMessage(), e));
+				throw new CoreException(Status.error(e.getMessage(), e));
 			}
 			setActivator(getHeaderValue(headers, Constants.BUNDLE_ACTIVATOR));
 			setBundleName(getHeaderValue(headers, Constants.BUNDLE_NAME));
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/BundleProjectService.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/BundleProjectService.java
index 71749c1..d66ec75 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/BundleProjectService.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/BundleProjectService.java
@@ -30,7 +30,6 @@
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.service.resolver.VersionRange;
@@ -44,7 +43,6 @@
 import org.eclipse.pde.core.project.IPackageExportDescription;
 import org.eclipse.pde.core.project.IPackageImportDescription;
 import org.eclipse.pde.core.project.IRequiredBundleDescription;
-import org.eclipse.pde.core.target.TargetBundle;
 import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.target.Messages;
 import org.eclipse.team.core.ScmUrlImportDescription;
@@ -286,7 +284,7 @@
 			}
 			return ManifestElement.parseBundleManifest(manifestStream, new Hashtable<String, String>(10));
 		} catch (BundleException | IOException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, TargetBundle.STATUS_INVALID_MANIFEST, NLS.bind(Messages.TargetBundle_ErrorReadingManifest, bundleLocation.getAbsolutePath()), e));
+			throw new CoreException(Status.error(NLS.bind(Messages.TargetBundle_ErrorReadingManifest, bundleLocation.getAbsolutePath()), e));
 		} finally {
 			closeZipFileAndStream(manifestStream, jarFile);
 		}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/PDEProject.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/PDEProject.java
index 050d311..7173de2 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/PDEProject.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/PDEProject.java
@@ -20,7 +20,6 @@
 import org.eclipse.core.resources.ProjectScope;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
@@ -139,10 +138,10 @@
 				// update model manager
 				PDECore.getDefault().getModelManager().bundleRootChanged(project);
 			} catch (BackingStoreException e) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, e.getMessage(), e));
+				throw new CoreException(Status.error(e.getMessage(), e));
 			}
 		} else {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, "Failed to retrieve project scope preference settings")); //$NON-NLS-1$
+			throw new CoreException(Status.error("Failed to retrieve project scope preference settings")); //$NON-NLS-1$
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/ProjectModifyOperation.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/ProjectModifyOperation.java
index 3caf7b2..9474ce2 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/ProjectModifyOperation.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/project/ProjectModifyOperation.java
@@ -30,7 +30,6 @@
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubMonitor;
@@ -214,7 +213,7 @@
 			try {
 				pref.flush();
 			} catch (BackingStoreException e) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.ProjectModifyOperation_2, e));
+				throw new CoreException(Status.error(Messages.ProjectModifyOperation_2, e));
 			}
 		}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/site/SiteObject.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/site/SiteObject.java
index 90ce6f6..29c7830 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/site/SiteObject.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/site/SiteObject.java
@@ -15,11 +15,9 @@
 
 import java.io.PrintWriter;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.PlatformObject;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.core.ModelChangedEvent;
-import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.PDECoreMessages;
 import org.eclipse.pde.internal.core.isite.ISite;
 import org.eclipse.pde.internal.core.isite.ISiteModel;
@@ -152,10 +150,7 @@
 	}
 
 	protected void throwCoreException(String message) throws CoreException {
-		Status status = new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.OK, message, null);
-		CoreException ce = new CoreException(status);
-		ce.fillInStackTrace();
-		throw ce;
+		throw new CoreException(Status.error(message));
 	}
 
 	public static String getWritableString(String source) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/DirectoryBundleContainer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/DirectoryBundleContainer.java
index 3fda88d..0ada54c 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/DirectoryBundleContainer.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/DirectoryBundleContainer.java
@@ -20,7 +20,6 @@
 import java.util.Objects;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.osgi.util.NLS;
@@ -28,7 +27,6 @@
 import org.eclipse.pde.core.target.TargetBundle;
 import org.eclipse.pde.core.target.TargetFeature;
 import org.eclipse.pde.internal.build.IPDEBuildConstants;
-import org.eclipse.pde.internal.core.PDECore;
 
 /**
  * A directory of bundles.
@@ -89,8 +87,7 @@
 					}).filter(Objects::nonNull) //
 					.toArray(TargetBundle[]::new);
 		}
-		throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
-				NLS.bind(Messages.DirectoryBundleContainer_1, dir.toString())));
+		throw new CoreException(Status.error(NLS.bind(Messages.DirectoryBundleContainer_1, dir.toString())));
 	}
 
 	@Override
@@ -110,8 +107,7 @@
 				}
 			}).filter(Objects::nonNull).toArray(TargetFeature[]::new);
 		}
-		throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
-				NLS.bind(Messages.DirectoryBundleContainer_1, dir.toString())));
+		throw new CoreException(Status.error(NLS.bind(Messages.DirectoryBundleContainer_1, dir.toString())));
 	}
 
 	/**
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ExportTargetJob.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ExportTargetJob.java
index 1e74079..5f14434 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ExportTargetJob.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ExportTargetJob.java
@@ -39,7 +39,6 @@
 import org.eclipse.pde.core.target.NameVersionDescriptor;
 import org.eclipse.pde.core.target.TargetBundle;
 import org.eclipse.pde.core.target.TargetFeature;
-import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.PDECoreMessages;
 
 /**
@@ -86,7 +85,7 @@
 			}
 			exportProfile(fTarget, fDestination, monitor);
 		} catch (CoreException e) {
-			return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, "Failed to export the target", e); //$NON-NLS-1$
+			return Status.error("Failed to export the target", e); //$NON-NLS-1$
 		} finally {
 			monitor.done();
 		}
@@ -115,7 +114,7 @@
 	private void setupDestination(IProgressMonitor monitor) throws CoreException {
 		fileSystem = EFS.getLocalFileSystem();
 		if (!fileSystem.canWrite()) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, "Destination directory not writable.")); //$NON-NLS-1$
+			throw new CoreException(Status.error("Destination directory not writable.")); //$NON-NLS-1$
 		}
 		IFileStore destination = fileSystem.getStore(fDestination);
 		featureDir = destination.getChild("features"); //$NON-NLS-1$ExportTargetJob
@@ -248,6 +247,7 @@
 		return result;
 	}
 
+	@SuppressWarnings("restriction")
 	private void exportProfile(ITargetDefinition target, URI destination, IProgressMonitor monitor) throws CoreException {
 		Repo2Runnable exporter = new Repo2Runnable();
 		exporter.addDestination(createRepoDescriptor(destination, P2TargetUtils.getProfileId(target), RepositoryDescriptor.KIND_METADATA));
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ExternalFileTargetHandle.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ExternalFileTargetHandle.java
index b90b2fd..bf413ca 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ExternalFileTargetHandle.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ExternalFileTargetHandle.java
@@ -21,13 +21,11 @@
 import java.io.OutputStream;
 import java.net.URI;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.URIUtil;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.pde.core.target.ITargetDefinition;
 import org.eclipse.pde.core.target.ITargetHandle;
-import org.eclipse.pde.internal.core.PDECore;
 
 /**
  * A handle to a target stored in a remote file (outside workspace) and accessed using its URI.
@@ -88,8 +86,7 @@
 			((TargetDefinition) definition).write(stream);
 			stream.close();
 		} catch (IOException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
-					NLS.bind(Messages.LocalTargetHandle_4, fFile.getName()), e));
+			throw new CoreException(Status.error(NLS.bind(Messages.LocalTargetHandle_4, fFile.getName()), e));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/FeatureBundleContainer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/FeatureBundleContainer.java
index 50d1be3..9693246 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/FeatureBundleContainer.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/FeatureBundleContainer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2009, 2020 IBM Corporation and others.
+ * Copyright (c) 2009, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -20,9 +20,7 @@
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
-import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.pde.core.target.ITargetDefinition;
@@ -133,28 +131,28 @@
 
 			TargetFeature[] features = resolveFeatures(definition, null);
 			if (features.length == 0) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.FeatureBundleContainer_1, fId)));
+				throw new CoreException(Status.error(NLS.bind(Messages.FeatureBundleContainer_1, fId)));
 			}
 			File location = new File(features[0].getLocation());
 			if (!location.exists()) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.FeatureBundleContainer_0, location.toString())));
+				throw new CoreException(Status.error(NLS.bind(Messages.FeatureBundleContainer_0, location.toString())));
 			}
 			File manifest = new File(location, ICoreConstants.FEATURE_FILENAME_DESCRIPTOR);
 			if (!manifest.exists() || !manifest.isFile()) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.FeatureBundleContainer_2, fId)));
+				throw new CoreException(Status.error(NLS.bind(Messages.FeatureBundleContainer_2, fId)));
 			}
 			model = ExternalFeatureModelManager.createModel(manifest);
 			if (model == null || !model.isLoaded()) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.FeatureBundleContainer_2, fId)));
+				throw new CoreException(Status.error(NLS.bind(Messages.FeatureBundleContainer_2, fId)));
 			}
 			// search bundles in plug-ins directory
 			ITargetPlatformService service = PDECore.getDefault().acquireService(ITargetPlatformService.class);
 			if (service == null) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.FeatureBundleContainer_4));
+				throw new CoreException(Status.error(Messages.FeatureBundleContainer_4));
 			}
 //			File dir = new File(manifest.getParentFile().getParentFile().getParentFile(), "plugins"); //$NON-NLS-1$
 //			if (!dir.exists() || !dir.isDirectory()) {
-//				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.FeatureBundleContainer_5, fId)));
+//				throw new CoreException(Status.error(NLS.bind(Messages.FeatureBundleContainer_5, fId)));
 //			}
 			if (monitor.isCanceled()) {
 				return new TargetBundle[0];
@@ -171,7 +169,7 @@
 					return new TargetBundle[0];
 				}
 				// only include if plug-in matches environment
-				if (isMatch(definition.getArch(), plugin.getArch(), Platform.getOSArch()) && isMatch(definition.getNL(), plugin.getNL(), Platform.getNL()) && isMatch(definition.getOS(), plugin.getOS(), Platform.getOS()) && isMatch(definition.getWS(), plugin.getWS(), Platform.getWS())) {
+				if (plugin.matchesEnvironment(definition)) {
 					matchInfos.add(new NameVersionDescriptor(plugin.getId(), plugin.getVersion()));
 				}
 			}
@@ -200,25 +198,6 @@
 		return new TargetFeature[0];
 	}
 
-	/**
-	 * Returns whether the given target environment setting matches that of a fragments.
-	 *
-	 * @param targetValue value in target definition
-	 * @param fragmentValue value in fragment
-	 * @param runningValue value of current running platform
-	 * @return whether the fragment should be considered
-	 */
-	private boolean isMatch(String targetValue, String fragmentValue, String runningValue) {
-		if (fragmentValue == null) {
-			// unspecified, so it is a match
-			return true;
-		}
-		if (targetValue == null) {
-			return runningValue.equals(fragmentValue);
-		}
-		return targetValue.equals(fragmentValue);
-	}
-
 	@Override
 	public boolean equals(Object o) {
 		if (o instanceof FeatureBundleContainer) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/IUBundleContainer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/IUBundleContainer.java
index dd379ce..d6a2054 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/IUBundleContainer.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/IUBundleContainer.java
@@ -294,7 +294,7 @@
 			IQuery<IInstallableUnit> query = QueryUtil.createIUQuery(fIds[i], fVersions[i]);
 			IQueryResult<IInstallableUnit> queryResult = profile.query(query, null);
 			if (queryResult.isEmpty()) {
-				status.add(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.IUBundleContainer_1, fIds[i] + " " + fVersions[i]))); //$NON-NLS-1$
+				status.add(Status.error(NLS.bind(Messages.IUBundleContainer_1, fIds[i] + " " + fVersions[i]))); //$NON-NLS-1$
 			} else {
 				result.add(queryResult.iterator().next());
 			}
@@ -400,7 +400,7 @@
 			Iterator<IInstallableUnit> it = queryResult.iterator();
 			// bail if the feature is no longer available.
 			if (!it.hasNext()) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.IUBundleContainer_1, fIds[i])));
+				throw new CoreException(Status.error(NLS.bind(Messages.IUBundleContainer_1, fIds[i])));
 			}
 			IInstallableUnit iu = it.next();
 			// if the version is different from the spec (up or down), record the change.
@@ -771,7 +771,7 @@
 			IQuery<IInstallableUnit> query = QueryUtil.createLatestQuery(QueryUtil.createIUQuery(fIds[j], fVersions[j]));
 			IQueryResult<IInstallableUnit> queryResult = repos.query(query, null);
 			if (queryResult.isEmpty()) {
-				status.add(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.IUBundleContainer_1, fIds[j] + " " + fVersions[j])));//$NON-NLS-1$
+				status.add(Status.error(NLS.bind(Messages.IUBundleContainer_1, fIds[j] + " " + fVersions[j])));//$NON-NLS-1$
 			} else {
 				result.add(queryResult.iterator().next());
 			}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/IULocationFactory.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/IULocationFactory.java
index 6005ecd..f8626bd 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/IULocationFactory.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/IULocationFactory.java
@@ -22,11 +22,9 @@
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.core.target.ITargetLocation;
 import org.eclipse.pde.core.target.ITargetLocationFactory;
-import org.eclipse.pde.internal.core.PDECore;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -50,7 +48,7 @@
 					.parse(new ByteArrayInputStream(serializedXML.getBytes(StandardCharsets.UTF_8)));
 			location = document.getDocumentElement();
 		} catch (Exception e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, e.getMessage(), e));
+			throw new CoreException(Status.error(e.getMessage(), e));
 		}
 
 		if (IUBundleContainer.TYPE.equals(type) && location != null) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/LocalTargetHandle.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/LocalTargetHandle.java
index 89015ff..5009529 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/LocalTargetHandle.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/LocalTargetHandle.java
@@ -26,7 +26,6 @@
 import java.net.URISyntaxException;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.util.NLS;
@@ -94,9 +93,9 @@
 				long stamp = Long.parseLong(lng);
 				return new LocalTargetHandle(stamp);
 			}
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.LocalTargetHandle_0, null));
+			throw new CoreException(Status.error(Messages.LocalTargetHandle_0));
 		} catch (NumberFormatException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.LocalTargetHandle_0, e));
+			throw new CoreException(Status.error(Messages.LocalTargetHandle_0, e));
 		}
 	}
 
@@ -121,7 +120,7 @@
 		try {
 			return new BufferedInputStream(new FileInputStream(getFile()));
 		} catch (FileNotFoundException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.LocalTargetHandle_1, e));
+			throw new CoreException(Status.error(Messages.LocalTargetHandle_1, e));
 		}
 	}
 
@@ -131,7 +130,7 @@
 			URI uri = new URI(SCHEME, getFile().getName(), null);
 			return uri.toString();
 		} catch (URISyntaxException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.LocalTargetHandle_2, e));
+			throw new CoreException(Status.error(Messages.LocalTargetHandle_2, e));
 		}
 	}
 
@@ -173,7 +172,7 @@
 		if (file.exists()) {
 			file.delete();
 			if (file.exists()) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.LocalTargetHandle_3, file.getName())));
+				throw new CoreException(Status.error(NLS.bind(Messages.LocalTargetHandle_3, file.getName())));
 			}
 		}
 		P2TargetUtils.deleteProfile(this);
@@ -188,9 +187,9 @@
 			}
 			return new BufferedOutputStream(new FileOutputStream(file));
 		} catch (FileNotFoundException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.LocalTargetHandle_1, e));
+			throw new CoreException(Status.error(Messages.LocalTargetHandle_1, e));
 		} catch (IOException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.LocalTargetHandle_5, e));
+			throw new CoreException(Status.error(Messages.LocalTargetHandle_5, e));
 		}
 	}
 
@@ -201,8 +200,7 @@
 		try {
 			stream.close();
 		} catch (IOException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
-					NLS.bind(Messages.LocalTargetHandle_4, getFile().getName()), e));
+			throw new CoreException(Status.error(NLS.bind(Messages.LocalTargetHandle_4, getFile().getName()), e));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/Messages.properties b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/Messages.properties
index 4c96a54..2465fc7 100755
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/Messages.properties
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/Messages.properties
@@ -61,7 +61,7 @@
 TargetFeature_FileDoesNotExist=File does not exist: {0}
 TargetPersistence38Helper_NoTargetLocationExtension=Could not find a org.eclipse.pde.core.targetLocations extension for type: {0}
 TargetPlatformService_0=Unable to restore target memento
-TargetPlatformService_1=Unrecognized target memento scheme
+TargetPlatformService_1=Unrecognized target memento scheme '{0}', supported scheme are {1}
 TargetPlatformService_2=Target extension does not exist: {0}
 TargetPlatformService_3=Error reading target extension file: {0}
 TargetPlatformService_4=Target extension file does not exist: {0}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/P2TargetUtils.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/P2TargetUtils.java
index 586ed21..e253e22 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/P2TargetUtils.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/P2TargetUtils.java
@@ -387,11 +387,11 @@
 		IProvisioningAgentProvider provider = PDECore.getDefault().acquireService(IProvisioningAgentProvider.class);
 		try {
 			if (provider == null) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_7));
+				throw new CoreException(Status.error(Messages.IUBundleContainer_7));
 			}
 			IProvisioningAgent agent = provider.createAgent(AGENT_LOCATION);
 			if (agent == null) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_7));
+				throw new CoreException(Status.error(Messages.IUBundleContainer_7));
 			}
 			// turn off the garbage collector for the PDE agent.  GC is managed on a coarser grain
 			GarbageCollector garbageCollector = (GarbageCollector) agent.getService(GarbageCollector.class.getName());
@@ -400,7 +400,7 @@
 			}
 			return agent;
 		} catch (ProvisionException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_7, e));
+			throw new CoreException(Status.error(Messages.IUBundleContainer_7, e));
 		}
 	}
 
@@ -414,7 +414,7 @@
 	public static IProvisioningAgent getGlobalAgent() throws CoreException {
 		IProvisioningAgent agent = PDECore.getDefault().acquireService(IProvisioningAgent.class);
 		if (agent == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_11));
+			throw new CoreException(Status.error(Messages.IUBundleContainer_11));
 		}
 		return agent;
 	}
@@ -428,7 +428,7 @@
 	public static IAgentLocation getAgentLocation() throws CoreException {
 		IAgentLocation result = (IAgentLocation) getAgent().getService(IAgentLocation.SERVICE_NAME);
 		if (result == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_10));
+			throw new CoreException(Status.error(Messages.IUBundleContainer_10));
 		}
 		return result;
 	}
@@ -442,7 +442,7 @@
 	public static IArtifactRepositoryManager getArtifactRepositoryManager() throws CoreException {
 		IArtifactRepositoryManager manager = (IArtifactRepositoryManager) getAgent().getService(IArtifactRepositoryManager.class.getName());
 		if (manager == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_3));
+			throw new CoreException(Status.error(Messages.IUBundleContainer_3));
 		}
 		return manager;
 	}
@@ -477,7 +477,7 @@
 	public static IEngine getEngine() throws CoreException {
 		IEngine engine = (IEngine) getAgent().getService(IEngine.class.getName());
 		if (engine == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_4));
+			throw new CoreException(Status.error(Messages.IUBundleContainer_4));
 		}
 		return engine;
 	}
@@ -491,7 +491,7 @@
 	public static GarbageCollector getGarbageCollector() throws CoreException {
 		GarbageCollector engine = (GarbageCollector) getAgent().getService(GarbageCollector.class.getName());
 		if (engine == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_9));
+			throw new CoreException(Status.error(Messages.IUBundleContainer_9));
 		}
 		return engine;
 	}
@@ -505,7 +505,7 @@
 	public static IPlanner getPlanner() throws CoreException {
 		IPlanner planner = (IPlanner) getAgent().getService(IPlanner.class.getName());
 		if (planner == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_5));
+			throw new CoreException(Status.error(Messages.IUBundleContainer_5));
 		}
 		return planner;
 	}
@@ -857,7 +857,7 @@
 		// create a new profile
 		IProfileRegistry registry = getProfileRegistry();
 		if (registry == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.AbstractTargetHandle_0));
+			throw new CoreException(Status.error(Messages.AbstractTargetHandle_0));
 		}
 		Map<String, String> properties = new HashMap<>();
 		properties.put(IProfile.PROP_INSTALL_FOLDER, INSTALL_FOLDERS.append(Long.toString(LocalTargetHandle.nextTimeStamp())).toOSString());
@@ -936,7 +936,7 @@
 	public static IProfileRegistry getProfileRegistry() throws CoreException {
 		IProfileRegistry result = (IProfileRegistry) getAgent().getService(IProfileRegistry.SERVICE_NAME);
 		if (result == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_8));
+			throw new CoreException(Status.error(Messages.IUBundleContainer_8));
 		}
 		return result;
 	}
@@ -960,7 +960,7 @@
 	public static IMetadataRepositoryManager getRepoManager() throws CoreException {
 		IMetadataRepositoryManager manager = (IMetadataRepositoryManager) getAgent().getService(IMetadataRepositoryManager.SERVICE_NAME);
 		if (manager == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_2));
+			throw new CoreException(Status.error(Messages.IUBundleContainer_2));
 		}
 		return manager;
 	}
@@ -1041,7 +1041,7 @@
 		if (installerPlan != null) {
 			// this plan requires an update to the installer first, log the fact and attempt
 			// to continue, we don't want to update the running SDK while provisioning a target
-			PDECore.log(new Status(IStatus.INFO, PDECore.PLUGIN_ID, Messages.IUBundleContainer_6));
+			PDECore.log(Status.info(Messages.IUBundleContainer_6));
 		}
 		subMonitor.split(10);
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ProfileBundleContainer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ProfileBundleContainer.java
index d57eb4a..0efabda 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ProfileBundleContainer.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/ProfileBundleContainer.java
@@ -30,7 +30,6 @@
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubMonitor;
@@ -116,13 +115,13 @@
 	protected TargetBundle[] resolveBundles(ITargetDefinition definition, IProgressMonitor monitor) throws CoreException {
 		String home = resolveHomeLocation().toOSString();
 		if (!new File(home).isDirectory()) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.ProfileBundleContainer_0, home)));
+			throw new CoreException(Status.error(NLS.bind(Messages.ProfileBundleContainer_0, home)));
 		}
 
 		File configurationArea = getConfigurationArea();
 		if (configurationArea != null) {
 			if (!configurationArea.isDirectory()) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.ProfileBundleContainer_2, home)));
+				throw new CoreException(Status.error(NLS.bind(Messages.ProfileBundleContainer_2, home)));
 			}
 		}
 
@@ -282,7 +281,8 @@
 			return file;
 		} else if (fConfiguration != null) {
 			// If the user specified config area does not exist throw an error
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.ProfileBundleContainer_2, configuration.toOSString())));
+			throw new CoreException(
+					Status.error(NLS.bind(Messages.ProfileBundleContainer_2, configuration.toOSString())));
 		}
 		return null;
 	}
@@ -320,14 +320,14 @@
 		// Get the configuration location
 		String home = resolveHomeLocation().toOSString();
 		if (!new File(home).isDirectory()) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.ProfileBundleContainer_0, home)));
+			throw new CoreException(Status.error(NLS.bind(Messages.ProfileBundleContainer_0, home)));
 		}
 		File configArea = getConfigurationArea();
 		if (configArea == null) {
 			configArea = new File(home);
 		}
 		if (!configArea.isDirectory()) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.ProfileBundleContainer_2, configArea)));
+			throw new CoreException(Status.error(NLS.bind(Messages.ProfileBundleContainer_2, configArea)));
 		}
 
 		// Location of the profile
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetDefinition.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetDefinition.java
index 99cb470..8593286 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetDefinition.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetDefinition.java
@@ -1272,7 +1272,7 @@
 							.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
 					Element root = document.getDocumentElement();
 					if (!root.getNodeName().equalsIgnoreCase(TargetDefinitionPersistenceHelper.LOCATION)) {
-						throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
+						throw new CoreException(Status.error(
 								NLS.bind(Messages.TargetDefinitionPersistenceHelper_WrongRootElementInXML, type, xml)));
 					}
 					root.setAttribute(TargetDefinitionPersistenceHelper.ATTR_LOCATION_TYPE, type);
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetDefinitionPersistenceHelper.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetDefinitionPersistenceHelper.java
index b91f013..18dc796 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetDefinitionPersistenceHelper.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetDefinitionPersistenceHelper.java
@@ -31,7 +31,6 @@
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.core.target.ITargetDefinition;
 import org.eclipse.pde.core.target.ITargetPlatformService;
@@ -157,7 +156,7 @@
 
 		Element root = doc.getDocumentElement();
 		if (!root.getNodeName().equalsIgnoreCase(ROOT)) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.TargetDefinitionPersistenceHelper_0));
+			throw new CoreException(Status.error(Messages.TargetDefinitionPersistenceHelper_0));
 		}
 
 		String version = null;
@@ -196,7 +195,7 @@
 		} else {
 			// Version doesn't match any known file structure, default to latest
 			String name = root.getAttribute(TargetDefinitionPersistenceHelper.ATTR_NAME);
-			PDECore.log(new Status(IStatus.WARNING, PDECore.PLUGIN_ID, MessageFormat.format(Messages.TargetDefinitionPersistenceHelper_2, version, name)));
+			PDECore.log(Status.warning(MessageFormat.format(Messages.TargetDefinitionPersistenceHelper_2, version, name)));
 			TargetPersistence38Helper.initFromDoc(definition, root);
 		}
 		definition.setDocument(doc);
@@ -206,7 +205,7 @@
 		if (fTargetService == null) {
 			fTargetService = PDECore.getDefault().acquireService(ITargetPlatformService.class);
 			if (fTargetService == null) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.TargetDefinitionPersistenceHelper_1));
+				throw new CoreException(Status.error(Messages.TargetDefinitionPersistenceHelper_1));
 			}
 		}
 		return fTargetService;
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPersistence36Helper.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPersistence36Helper.java
index c0ddd09..36ffa47 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPersistence36Helper.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPersistence36Helper.java
@@ -46,7 +46,7 @@
 	<location includeAllPlatforms="false" includeMode="slicer" includeSource="true" type="InstallableUnit">
 	<unit id="org.eclipse.egit.feature.group" version="0.11.3"/>
 	<unit id="org.eclipse.jgit.feature.group" version="0.11.3"/>
-	<repository location="http://download.eclipse.org/releases/indigo"/>
+	<repository location="https://download.eclipse.org/releases/indigo"/>
 	</location>
 	</locations>
 	</target>
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPersistence38Helper.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPersistence38Helper.java
index eb9bab7..20b1c50 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPersistence38Helper.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPersistence38Helper.java
@@ -26,7 +26,6 @@
 import javax.xml.transform.stream.StreamResult;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.util.NLS;
@@ -247,7 +246,7 @@
 				// Convert the xml to a string to pass to the extension
 				ITargetLocationFactory locFactory = TargetLocationTypeManager.getInstance().getTargetLocationFactory(type);
 				if (locFactory == null) {
-					throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.TargetPersistence38Helper_NoTargetLocationExtension, type)));
+					throw new CoreException(Status.error(NLS.bind(Messages.TargetPersistence38Helper_NoTargetLocationExtension, type)));
 				}
 				StreamResult result = new StreamResult(new StringWriter());
 				Transformer transformer = TransformerFactory.newInstance().newTransformer();
@@ -255,7 +254,7 @@
 				transformer.transform(new DOMSource(location), result);
 				container = locFactory.getTargetLocation(type, result.getWriter().toString());
 			} catch (TransformerException | TransformerFactoryConfigurationError e) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.TargetDefinitionPersistenceHelper_0, e));
+				throw new CoreException(Status.error(Messages.TargetDefinitionPersistenceHelper_0, e));
 			}
 			break;
 		}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPlatformService.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPlatformService.java
index 8e228a2..6ed5dd2 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPlatformService.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/TargetPlatformService.java
@@ -32,6 +32,8 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceProxy;
@@ -39,10 +41,12 @@
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.ICoreRunnable;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
@@ -172,9 +176,14 @@
 				}
 			}
 		} catch (URISyntaxException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.TargetPlatformService_0, e));
+			throw new CoreException(Status.error(Messages.TargetPlatformService_0, e));
 		}
-		throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.TargetPlatformService_1, null));
+		throw new CoreException(
+				Status.error(NLS.bind(Messages.TargetPlatformService_1, memento,
+						Stream.of(WorkspaceFileTargetHandle.SCHEME, LocalTargetHandle.SCHEME,
+								ExternalFileTargetHandle.SCHEME, RemoteTargetHandle.SCHEME)
+								.collect(Collectors.joining(", "))), //$NON-NLS-1$
+						null));
 	}
 
 	@Override
@@ -322,7 +331,7 @@
 			target = handle.getTargetDefinition();
 		}
 
-		setWorkspaceTargetDefinition(target);
+		setWorkspaceTargetDefinition(target, true);
 		return target;
 	}
 
@@ -333,16 +342,30 @@
 	 * as it should only be called from LoadTargetDefinitionJob which does additional
 	 * steps to reset the target.
 	 *
-	 * @param target the new workspace target definition
+	 * @param target
+	 *            the new workspace target definition
+	 * @param asyncEvents
+	 *            to notify listener asynchronously
 	 */
-	public void setWorkspaceTargetDefinition(ITargetDefinition target) {
+	public void setWorkspaceTargetDefinition(ITargetDefinition target, boolean asyncEvents) {
 		boolean changed = !Objects.equals(fWorkspaceTarget, target);
 		fWorkspaceTarget = target;
 		if (changed) {
-			IEclipseContext context = EclipseContextFactory.getServiceContext(PDECore.getDefault().getBundleContext());
-			IEventBroker broker = context.get(IEventBroker.class);
-			if (broker != null) {
-				broker.send(TargetEvents.TOPIC_WORKSPACE_TARGET_CHANGED, target);
+			ICoreRunnable notify = monitor -> {
+				IEclipseContext context = EclipseContextFactory.getServiceContext(PDECore.getDefault().getBundleContext());
+				IEventBroker broker = context.get(IEventBroker.class);
+				if (broker != null) {
+					broker.send(TargetEvents.TOPIC_WORKSPACE_TARGET_CHANGED, target);
+				}
+			};
+			if (asyncEvents) {
+				Job.create("Sending 'workspace target changed' event", notify).schedule(); //$NON-NLS-1$
+			} else {
+				try {
+					notify.run(new NullProgressMonitor());
+				} catch (CoreException e) {
+					PDECore.log(e);
+				}
 			}
 		}
 	}
@@ -414,7 +437,7 @@
 	public void loadTargetDefinition(ITargetDefinition definition, String targetExtensionId) throws CoreException {
 		IConfigurationElement elem = PDECore.getDefault().getTargetProfileManager().getTarget(targetExtensionId);
 		if (elem == null) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.TargetPlatformService_2, targetExtensionId)));
+			throw new CoreException(Status.error(NLS.bind(Messages.TargetPlatformService_2, targetExtensionId)));
 		}
 		String path = elem.getAttribute("definition"); //$NON-NLS-1$
 		String symbolicName = elem.getDeclaringExtension().getContributor().getName();
@@ -423,11 +446,10 @@
 			try {
 				((TargetDefinition) definition).setContents(new BufferedInputStream(url.openStream()));
 			} catch (IOException e) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID,
-						NLS.bind(Messages.TargetPlatformService_3, path), e));
+				throw new CoreException(Status.error(NLS.bind(Messages.TargetPlatformService_3, path), e));
 			}
 		} else {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(Messages.TargetPlatformService_4, path)));
+			throw new CoreException(Status.error(NLS.bind(Messages.TargetPlatformService_4, path)));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/WorkspaceFileTargetHandle.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/WorkspaceFileTargetHandle.java
index ba824d4..c6ed063 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/WorkspaceFileTargetHandle.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/WorkspaceFileTargetHandle.java
@@ -27,7 +27,6 @@
 import org.eclipse.core.runtime.Status;
 import org.eclipse.pde.core.target.ITargetDefinition;
 import org.eclipse.pde.core.target.ITargetHandle;
-import org.eclipse.pde.internal.core.PDECore;
 
 /**
  * A handle to a target stored in the workspace as a <code>.target</code> file.
@@ -71,7 +70,7 @@
 			URI uri = new URI(SCHEME, fFile.getFullPath().toPortableString(), null);
 			return uri.toString();
 		} catch (URISyntaxException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.WorkspaceFileTargetHandle_0, e));
+			throw new CoreException(Status.error(Messages.WorkspaceFileTargetHandle_0, e));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/update/configurator/Utils.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/update/configurator/Utils.java
index c36e75d..0edb094 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/update/configurator/Utils.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/update/configurator/Utils.java
@@ -31,7 +31,7 @@
 	public static boolean isWindows = System.getProperty("os.name").startsWith("Win"); //$NON-NLS-1$ //$NON-NLS-2$
 
 	public static IStatus newStatus(String message, Throwable e) {
-		return new Status(IStatus.ERROR, "org.eclipse.update.configurator", IStatus.OK, message, e); //$NON-NLS-1$
+		return Status.error(message, e);
 	}
 
 	public static void log(String message) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/ManifestUtils.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/ManifestUtils.java
index a2bb963..4536b1e 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/ManifestUtils.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/ManifestUtils.java
@@ -139,7 +139,7 @@
 			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, STATUS_CODE_NOT_A_BUNDLE_MANIFEST, NLS.bind(UtilMessages.ErrorReadingManifest, bundleLocation.getAbsolutePath()), null));
 
 		} catch (BundleException | IOException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, 0, NLS.bind(UtilMessages.ErrorReadingManifest, bundleLocation.getAbsolutePath()), e));
+			throw new CoreException(Status.error(  NLS.bind(UtilMessages.ErrorReadingManifest, bundleLocation.getAbsolutePath()), e));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/VMUtil.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/VMUtil.java
index 47714b1..c36d2a6 100755
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/VMUtil.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/VMUtil.java
@@ -26,7 +26,6 @@
 import org.eclipse.jdt.launching.environments.IExecutionEnvironment;
 import org.eclipse.jdt.launching.environments.IExecutionEnvironmentsManager;
 import org.eclipse.osgi.util.NLS;
-import org.eclipse.pde.internal.core.PDECore;
 
 public class VMUtil {
 
@@ -102,6 +101,6 @@
 	}
 
 	public static IStatus createErrorStatus(String message) {
-		return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.OK, message, null);
+		return Status.error(message, null);
 	}
 }
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/VersionUtil.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/VersionUtil.java
index 14ea45a..b877c84 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/VersionUtil.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/util/VersionUtil.java
@@ -17,7 +17,6 @@
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.service.resolver.VersionRange;
 import org.eclipse.pde.core.plugin.IMatchRules;
-import org.eclipse.pde.internal.core.PDECore;
 import org.osgi.framework.Version;
 
 public class VersionUtil {
@@ -31,7 +30,7 @@
 				new Version(versionString.trim());
 			}
 		} catch (IllegalArgumentException e) {
-			return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.ERROR, UtilMessages.BundleErrorReporter_InvalidFormatInBundleVersion, e);
+			return Status.error(UtilMessages.BundleErrorReporter_InvalidFormatInBundleVersion, e);
 		}
 		return Status.OK_STATUS;
 	}
@@ -40,7 +39,7 @@
 		try {
 			new VersionRange(versionRangeString);
 		} catch (IllegalArgumentException e) {
-			return new Status(IStatus.ERROR, PDECore.PLUGIN_ID, IStatus.ERROR, UtilMessages.BundleErrorReporter_invalidVersionRangeFormat, e);
+			return Status.error(UtilMessages.BundleErrorReporter_invalidVersionRangeFormat, e);
 		}
 		return Status.OK_STATUS;
 	}
diff --git a/ui/org.eclipse.pde.core/src_ant/org/eclipse/pde/internal/core/ant/TargetPlatformProvisionTask.java b/ui/org.eclipse.pde.core/src_ant/org/eclipse/pde/internal/core/ant/TargetPlatformProvisionTask.java
index af2b92b..eb4b7f4 100644
--- a/ui/org.eclipse.pde.core/src_ant/org/eclipse/pde/internal/core/ant/TargetPlatformProvisionTask.java
+++ b/ui/org.eclipse.pde.core/src_ant/org/eclipse/pde/internal/core/ant/TargetPlatformProvisionTask.java
@@ -26,7 +26,6 @@
 import org.eclipse.pde.core.target.ITargetDefinition;
 import org.eclipse.pde.internal.build.BundleHelper;
 import org.eclipse.pde.internal.build.tasks.TaskHelper;
-import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.PDECoreMessages;
 import org.eclipse.pde.internal.core.target.ExportTargetJob;
 import org.eclipse.pde.internal.core.target.TargetPlatformService;
@@ -90,13 +89,13 @@
 
 	private void run() throws CoreException {
 		if (null == targetFile) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.TargetPlatformProvisionTask_ErrorDefinitionNotSet));
+			throw new CoreException(Status.error(PDECoreMessages.TargetPlatformProvisionTask_ErrorDefinitionNotSet));
 		}
 		if (!targetFile.isFile() || !targetFile.canRead()) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, NLS.bind(PDECoreMessages.TargetPlatformProvisionTask_ErrorDefinitionNotFoundAtSpecifiedLocation, targetFile)));
+			throw new CoreException(Status.error(NLS.bind(PDECoreMessages.TargetPlatformProvisionTask_ErrorDefinitionNotFoundAtSpecifiedLocation, targetFile)));
 		}
 		if (null == destinationDirectory) {
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, PDECoreMessages.TargetPlatformProvisionTask_ErrorDestinationNotSet));
+			throw new CoreException(Status.error(PDECoreMessages.TargetPlatformProvisionTask_ErrorDestinationNotSet));
 		}
 
 		final ITargetDefinition targetDefinition = TargetPlatformService.getDefault().getTarget(targetFile.toURI()).getTargetDefinition();
diff --git a/ui/org.eclipse.pde.core/text/org/eclipse/pde/internal/core/text/XMLEditingModel.java b/ui/org.eclipse.pde.core/text/org/eclipse/pde/internal/core/text/XMLEditingModel.java
index 705d813..c995704 100644
--- a/ui/org.eclipse.pde.core/text/org/eclipse/pde/internal/core/text/XMLEditingModel.java
+++ b/ui/org.eclipse.pde.core/text/org/eclipse/pde/internal/core/text/XMLEditingModel.java
@@ -47,12 +47,12 @@
 	public void load(InputStream source, boolean outOfSync) {
 		try {
 			fLoaded = true;
-			status = new Status(IStatus.OK, PDECore.PLUGIN_ID, null);
+			status = Status.OK_STATUS;
 			SAXParserWrapper parser = new SAXParserWrapper();
 			parser.parse(source, createDocumentHandler(this, true));
 		} catch (SAXException e) {
 			fLoaded = false;
-			status = new Status(IStatus.ERROR, PDECore.PLUGIN_ID, e.getMessage(), e);
+			status = Status.error(e.getMessage(), e);
 		} catch (IOException | ParserConfigurationException | FactoryConfigurationError e) {
 			fLoaded = false;
 		}
diff --git a/ui/org.eclipse.pde.genericeditor.extension.tests/pom.xml b/ui/org.eclipse.pde.genericeditor.extension.tests/pom.xml
index 18e0753..820ad28 100644
--- a/ui/org.eclipse.pde.genericeditor.extension.tests/pom.xml
+++ b/ui/org.eclipse.pde.genericeditor.extension.tests/pom.xml
@@ -13,11 +13,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>tests-pom</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../tests-pom/</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.genericeditor.extension.tests</artifactId>
   <version>1.1.0-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
diff --git a/ui/org.eclipse.pde.genericeditor.extension/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.genericeditor.extension/META-INF/MANIFEST.MF
index 24c144b..ab9d1e2 100644
--- a/ui/org.eclipse.pde.genericeditor.extension/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.genericeditor.extension/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.pde.genericeditor.extension;singleton:=true
-Bundle-Version: 1.1.100.qualifier
+Bundle-Version: 1.1.200.qualifier
 Bundle-Localization: plugin
 Require-Bundle: org.eclipse.core.runtime,
  org.eclipse.jface.text,
diff --git a/ui/org.eclipse.pde.genericeditor.extension/pom.xml b/ui/org.eclipse.pde.genericeditor.extension/pom.xml
index 345a334..dc41a8c 100644
--- a/ui/org.eclipse.pde.genericeditor.extension/pom.xml
+++ b/ui/org.eclipse.pde.genericeditor.extension/pom.xml
@@ -13,12 +13,11 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde.ui</groupId>
   <artifactId>org.eclipse.pde.genericeditor.extension</artifactId>
-  <version>1.1.100-SNAPSHOT</version>
+  <version>1.1.200-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/p2/UpdateJob.java b/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/p2/UpdateJob.java
index 17d1992..075dc46 100644
--- a/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/p2/UpdateJob.java
+++ b/ui/org.eclipse.pde.genericeditor.extension/src/org/eclipse/pde/internal/genericeditor/target/extension/p2/UpdateJob.java
@@ -22,7 +22,6 @@
 import org.eclipse.pde.internal.genericeditor.target.extension.model.LocationNode;
 import org.eclipse.pde.internal.genericeditor.target.extension.model.RepositoryCache;
 import org.eclipse.pde.internal.genericeditor.target.extension.model.UnitNode;
-import org.osgi.framework.FrameworkUtil;
 
 /**
  * Fetching P2 repository information is a costly operation time-wise. Thus we
@@ -41,7 +40,7 @@
 	protected IStatus run(IProgressMonitor monitor) {
 		List<UnitNode> list = RepositoryCache.getDefault().fetchP2UnitsFromRepo(node.getRepositoryLocation(), true);
 		if (list == null) {
-			return new Status(IStatus.ERROR, FrameworkUtil.getBundle(UpdateJob.class).getSymbolicName(), Messages.UpdateJob_ErrorMessage);
+			return Status.error(Messages.UpdateJob_ErrorMessage);
 		}
 		return Status.OK_STATUS;
 	}
diff --git a/ui/org.eclipse.pde.junit.runtime.tests/pom.xml b/ui/org.eclipse.pde.junit.runtime.tests/pom.xml
index 122cde5..42235b1 100644
--- a/ui/org.eclipse.pde.junit.runtime.tests/pom.xml
+++ b/ui/org.eclipse.pde.junit.runtime.tests/pom.xml
@@ -19,11 +19,10 @@
 	<modelVersion>4.0.0</modelVersion>
 	<parent>
 		<artifactId>tests-pom</artifactId>
-		<groupId>eclipse.pde.ui</groupId>
+		<groupId>org.eclipse.pde</groupId>
 		<version>4.23.0-SNAPSHOT</version>
 		<relativePath>../../tests-pom/</relativePath>
 	</parent>
-	<groupId>org.eclipse.pde</groupId>
 	<artifactId>org.eclipse.pde.junit.runtime.tests</artifactId>
 	<version>3.6.600-SNAPSHOT</version>
 	<packaging>eclipse-test-plugin</packaging>
diff --git a/ui/org.eclipse.pde.junit.runtime/pom.xml b/ui/org.eclipse.pde.junit.runtime/pom.xml
index b5642e6..efbe618 100644
--- a/ui/org.eclipse.pde.junit.runtime/pom.xml
+++ b/ui/org.eclipse.pde.junit.runtime/pom.xml
@@ -13,11 +13,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.junit.runtime</artifactId>
   <version>3.6.100-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ui/org.eclipse.pde.launching/forceQualifierUpdate.txt b/ui/org.eclipse.pde.launching/forceQualifierUpdate.txt
new file mode 100644
index 0000000..e2fbf2f
--- /dev/null
+++ b/ui/org.eclipse.pde.launching/forceQualifierUpdate.txt
@@ -0,0 +1,3 @@
+# To force a version qualifier update add the bug here
+
+Bug 578351 - Lambda generation order is unstable in ecj 
\ No newline at end of file
diff --git a/ui/org.eclipse.pde.launching/pom.xml b/ui/org.eclipse.pde.launching/pom.xml
index fdff78f..597866c 100644
--- a/ui/org.eclipse.pde.launching/pom.xml
+++ b/ui/org.eclipse.pde.launching/pom.xml
@@ -13,11 +13,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.launching</artifactId>
   <version>3.9.600-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/PDELaunchingPlugin.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/PDELaunchingPlugin.java
index 4413dcc..516e2d6 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/PDELaunchingPlugin.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/PDELaunchingPlugin.java
@@ -79,7 +79,7 @@
 		if (e instanceof CoreException)
 			status = ((CoreException) e).getStatus();
 		else
-			status = new Status(IStatus.ERROR, getPluginId(), IStatus.OK, e.getMessage(), e);
+			status = Status.error(e.getMessage(), e);
 		log(status);
 	}
 
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java
index 57e588d..77e0d3a 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2021 IBM Corporation and others.
+ * Copyright (c) 2007, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -11,30 +11,50 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     EclipseSource Corporation - ongoing enhancements
- *     Hannes Wellmann - Bug 576885: Unify methods to parse bundle-sets from launch-configs
+ *     Hannes Wellmann - Bug 576885 - Unify methods to parse bundle-sets from launch-configs
+ *     Hannes Wellmann - Bug 577118 - Handle multiple Plug-in versions in launching facility
+ *     Hannes Wellmann - Bug 576886 - Clean up and improve BundleLaunchHelper and extract String literal constants
+ *     Hannes Wellmann - Bug 576887 - Handle multiple versions of features and plug-ins for feature-launches
+ *     Hannes Wellmann - Bug 576888, Bug 576889 - Consider included child-features and required dependency-features for feature-launches
+ *     Hannes Wellmann - Bug 576890 - Ignore included features/plug-ins not matching target-environment
  *******************************************************************************/
 package org.eclipse.pde.internal.launching.launcher;
 
 import static java.util.Collections.emptySet;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.groupingBy;
 
 import java.util.*;
-import java.util.Map.Entry;
-import java.util.function.Function;
-import java.util.function.Predicate;
+import java.util.function.*;
+import java.util.stream.Stream;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
 import org.eclipse.osgi.service.resolver.BundleDescription;
 import org.eclipse.pde.core.plugin.*;
+import org.eclipse.pde.core.target.ITargetDefinition;
+import org.eclipse.pde.core.target.ITargetPlatformService;
 import org.eclipse.pde.internal.build.IPDEBuildConstants;
 import org.eclipse.pde.internal.core.*;
 import org.eclipse.pde.internal.core.ifeature.*;
+import org.eclipse.pde.internal.core.util.VersionUtil;
 import org.eclipse.pde.internal.launching.IPDEConstants;
 import org.eclipse.pde.launching.IPDELauncherConstants;
 import org.osgi.framework.Version;
 
 public class BundleLauncherHelper {
 
+	private BundleLauncherHelper() { // static use only
+	}
+
+	public static final char VERSION_SEPARATOR = '*';
+	private static final char START_LEVEL_SEPARATOR = '@';
+	private static final String AUTO_START_SEPARATOR = ":"; //$NON-NLS-1$  
+	private static final String DEFAULT = "default"; //$NON-NLS-1$
+	private static final String DEFAULT_START_LEVELS = DEFAULT + AUTO_START_SEPARATOR + DEFAULT;
+	private static final String FEATURE_PLUGIN_RESOLUTION_SEPARATOR = ":"; //$NON-NLS-1$  
+	private static final String FEATURES_ADDITIONAL_PLUGINS_DATA_SEPARATOR = ":"; //$NON-NLS-1$  
+
 	/**
 	 * When creating a mapping of bundles to their start levels, update configurator is set
 	 * to auto start at level three.  However, if at launch time we are launching with both
@@ -43,16 +63,10 @@
 	 */
 	public static final String DEFAULT_UPDATE_CONFIGURATOR_START_LEVEL_TEXT = "3"; //$NON-NLS-1$
 	public static final String DEFAULT_UPDATE_CONFIGURATOR_AUTO_START_TEXT = "true"; //$NON-NLS-1$
-	public static final String DEFAULT_UPDATE_CONFIGURATOR_START_LEVEL = DEFAULT_UPDATE_CONFIGURATOR_START_LEVEL_TEXT + ":" + DEFAULT_UPDATE_CONFIGURATOR_AUTO_START_TEXT; //$NON-NLS-1$
-
-	public static final char VERSION_SEPARATOR = '*';
+	public static final String DEFAULT_UPDATE_CONFIGURATOR_START_LEVEL = DEFAULT_UPDATE_CONFIGURATOR_START_LEVEL_TEXT + AUTO_START_SEPARATOR + DEFAULT_UPDATE_CONFIGURATOR_AUTO_START_TEXT;
 
 	public static Map<IPluginModelBase, String> getWorkspaceBundleMap(ILaunchConfiguration configuration) throws CoreException {
-		return getWorkspaceBundleMap(configuration, null);
-	}
-
-	public static Map<IPluginModelBase, String> getTargetBundleMap(ILaunchConfiguration configuration) throws CoreException {
-		return getTargetBundleMap(configuration, null);
+		return getWorkspaceBundleMap(configuration, new HashMap<>());
 	}
 
 	public static Map<IPluginModelBase, String> getMergedBundleMap(ILaunchConfiguration configuration, boolean osgi) throws CoreException {
@@ -66,7 +80,7 @@
 				Map<IPluginModelBase, String> map = new LinkedHashMap<>();
 				IPluginModelBase[] models = PluginRegistry.getActiveModels();
 				for (IPluginModelBase model : models) {
-					addBundleToMap(map, model, "default:default"); //$NON-NLS-1$
+					addBundleToMap(map, model, DEFAULT_START_LEVELS);
 				}
 				return map;
 			}
@@ -79,91 +93,51 @@
 			return getMergedBundleMapFeatureBased(wc, osgi);
 		}
 
-		Set<String> set = new HashSet<>();
-		Map<IPluginModelBase, String> map = getWorkspaceBundleMap(wc, set);
-		map.putAll(getTargetBundleMap(wc, set));
+		return getAllSelectedPluginBundles(wc);
+	}
+
+	public static Map<IPluginModelBase, String> getAllSelectedPluginBundles(ILaunchConfiguration config) throws CoreException {
+		Map<String, List<Version>> idVersions = new HashMap<>();
+		Map<IPluginModelBase, String> map = getWorkspaceBundleMap(config, idVersions);
+		map.putAll(getTargetBundleMap(config, idVersions));
 		return map;
 	}
 
+	// --- feature based launches ---
+
 	private static Map<IPluginModelBase, String> getMergedBundleMapFeatureBased(ILaunchConfiguration configuration, boolean osgi) throws CoreException {
 
-		// Get the default location settings
-		String defaultLocation = configuration.getAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
 		String defaultPluginResolution = configuration.getAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_WORKSPACE);
+		ITargetDefinition target = PDECore.getDefault().acquireService(ITargetPlatformService.class).getWorkspaceTargetDefinition();
 
-		// Get all available features
-		HashMap<String, IFeatureModel> workspaceFeatureMap = new HashMap<>();
-		HashMap<String, IFeatureModel> externalFeatureMap = new HashMap<>();
-
-		FeatureModelManager fmm = PDECore.getDefault().getFeatureModelManager();
-		IFeatureModel[] workspaceFeatureModels = fmm.getWorkspaceModels();
-		for (IFeatureModel workspaceFeatureModel : workspaceFeatureModels) {
-			String id = workspaceFeatureModel.getFeature().getId();
-			workspaceFeatureMap.put(id, workspaceFeatureModel);
-		}
-
-		IFeatureModel[] externalFeatureModels = fmm.getExternalModels();
-		for (IFeatureModel externalFeatureModel : externalFeatureModels) {
-			String id = externalFeatureModel.getFeature().getId();
-			externalFeatureMap.put(id, externalFeatureModel);
-		}
-
-		// Get the selected features and their plugin resolution
-		Map<String, String> featureResolutionMap = new HashMap<>();
-		Set<String> selectedFeatures = configuration.getAttribute(IPDELauncherConstants.SELECTED_FEATURES, (Set<String>) null);
-		if (selectedFeatures != null) {
-			for (String currentSelected : selectedFeatures) {
-				String[] attributes = currentSelected.split(":"); //$NON-NLS-1$
-				if (attributes.length > 1) {
-					featureResolutionMap.put(attributes[0], attributes[1]);
-				}
-			}
-		}
+		Map<IFeature, String> feature2resolution = getSelectedFeatures(configuration, target);
 
 		// Get the feature model for each selected feature id and resolve its plugins
 		Set<IPluginModelBase> launchPlugins = new HashSet<>();
-		for (Entry<String, String> entry : featureResolutionMap.entrySet()) {
-			String id = entry.getKey();
-			IFeatureModel featureModel = null;
-			if (IPDELauncherConstants.LOCATION_WORKSPACE.equalsIgnoreCase(defaultLocation)) {
-				featureModel = workspaceFeatureMap.get(id);
-			}
-			if (featureModel == null || IPDELauncherConstants.LOCATION_EXTERNAL.equalsIgnoreCase(defaultLocation)) {
-				if (externalFeatureMap.containsKey(id)) {
-					featureModel = externalFeatureMap.get(id);
-				}
-			}
-			if (featureModel == null) {
-				continue;
-			}
 
-			IFeaturePlugin[] featurePlugins = featureModel.getFeature().getPlugins();
-			String pluginResolution = entry.getValue();
+		feature2resolution.forEach((feature, pluginResolution) -> {
 			if (IPDELauncherConstants.LOCATION_DEFAULT.equalsIgnoreCase(pluginResolution)) {
 				pluginResolution = defaultPluginResolution;
 			}
-
+			IFeaturePlugin[] featurePlugins = feature.getPlugins();
 			for (IFeaturePlugin featurePlugin : featurePlugins) {
-				ModelEntry modelEntry = PluginRegistry.findEntry(featurePlugin.getId());
-				if (modelEntry != null) {
-					IPluginModelBase model = findModel(modelEntry, featurePlugin.getVersion(), pluginResolution);
-					if (model != null)
-						launchPlugins.add(model);
-				}
-			}
-
-			IFeatureImport[] featureImports = featureModel.getFeature().getImports();
-			for (IFeatureImport featureImport : featureImports) {
-				if (featureImport.getType() == IFeatureImport.PLUGIN) {
-					ModelEntry modelEntry = PluginRegistry.findEntry(featureImport.getId());
-					if (modelEntry != null) {
-						IPluginModelBase model = findModel(modelEntry, featureImport.getVersion(), pluginResolution);
-						if (model != null)
-							launchPlugins.add(model);
+				if (featurePlugin.matchesEnvironment(target)) {
+					IPluginModelBase plugin = getIncludedPlugin(featurePlugin.getId(), featurePlugin.getVersion(), pluginResolution);
+					if (plugin != null) {
+						launchPlugins.add(plugin);
 					}
 				}
 			}
-		}
+			IFeatureImport[] featureImports = feature.getImports();
+			for (IFeatureImport featureImport : featureImports) {
+				if (featureImport.getType() == IFeatureImport.PLUGIN) {
+					IPluginModelBase plugin = getRequiredPlugin(featureImport.getId(), featureImport.getVersion(), featureImport.getMatch(), pluginResolution);
+					if (plugin != null) {
+						launchPlugins.add(plugin);
+					}
+				}
+			}
+		});
 
 		Map<IPluginModelBase, AdditionalPluginData> additionalPlugins = getAdditionalPlugins(configuration, true);
 		launchPlugins.addAll(additionalPlugins.keySet());
@@ -172,221 +146,300 @@
 		if (!osgi) {
 			String[] applicationIds = RequirementHelper.getApplicationRequirements(configuration);
 			for (String applicationId : applicationIds) {
-				ModelEntry modelEntry = PluginRegistry.findEntry(applicationId);
-				if (modelEntry != null) {
-					IPluginModelBase model = findModel(modelEntry, null, defaultPluginResolution);
-					if (model != null)
-						launchPlugins.add(model);
+				IPluginModelBase plugin = getRequiredPlugin(applicationId, null, IMatchRules.NONE, defaultPluginResolution);
+				if (plugin != null) {
+					launchPlugins.add(plugin);
 				}
 			}
 		}
-
 		// Get all required plugins
-		Set<BundleDescription> additionalBundles = DependencyManager.getDependencies(launchPlugins, false);
+		Set<BundleDescription> additionalBundles = DependencyManager.getDependencies(launchPlugins, DependencyManager.Options.INCLUDE_NON_TEST_FRAGMENTS);
 		for (BundleDescription bundle : additionalBundles) {
-			ModelEntry modelEntry = PluginRegistry.findEntry(bundle.getSymbolicName());
-			if (modelEntry != null) {
-				IPluginModelBase model = findModel(modelEntry, bundle.getVersion().toString(), defaultPluginResolution);
-				if (model != null)
-					launchPlugins.add(model);
-			}
+			IPluginModelBase plugin = getRequiredPlugin(bundle.getSymbolicName(), bundle.getVersion().toString(), IMatchRules.PERFECT, defaultPluginResolution);
+			launchPlugins.add(Objects.requireNonNull(plugin));// should never be null
 		}
 
-		//remove conflicting duplicates - if they have same version or both are singleton
-		HashMap<String, IPluginModelBase> pluginMap = new HashMap<>();
-		Set<IPluginModelBase> pluginSet = new HashSet<>();
-		List<IPluginModelBase> workspaceModels = null;
-		for (IPluginModelBase model : launchPlugins) {
-			String id = model.getPluginBase().getId();
-			if (pluginMap.containsKey(id)) {
-				IPluginModelBase existing = pluginMap.get(id);
-				if (model.getPluginBase().getVersion().equalsIgnoreCase(existing.getPluginBase().getVersion()) || (isSingleton(model) && isSingleton(existing))) {
-					if (workspaceModels == null)
-						workspaceModels = Arrays.asList(PluginRegistry.getWorkspaceModels());
-					if (!workspaceModels.contains(existing)) { //if existing model is external
-						pluginSet.add(model);// launch the workspace model
-						continue;
-					}
-				}
-			}
-			pluginSet.add(model);
-		}
-		pluginMap.clear();
-
 		// Create the start levels for the selected plugins and add them to the map
 		Map<IPluginModelBase, String> map = new LinkedHashMap<>();
-		for (IPluginModelBase model : pluginSet) {
+		for (IPluginModelBase model : launchPlugins) {
 			AdditionalPluginData additionalPluginData = additionalPlugins.get(model);
-			String startLevels = (additionalPluginData != null) ? additionalPluginData.startLevels() : "default:default"; //$NON-NLS-1$
-			addBundleToMap(map, model, startLevels);
+			String startLevels = additionalPluginData != null ? additionalPluginData.startLevels() : DEFAULT_START_LEVELS;
+			addBundleToMap(map, model, startLevels); // might override data of plug-ins included by feature
 		}
 		return map;
 	}
 
-	/**
-	 * Finds the best candidate model from the <code>resolution</code> location. If the model is not found there,
-	 * alternate location is explored before returning <code>null</code>.
-	 * @param modelEntry
-	 * @param version
-	 * @param location
-	 * @return model
-	 */
-	private static IPluginModelBase findModel(ModelEntry modelEntry, String version, String location) {
-		IPluginModelBase model = null;
-		if (IPDELauncherConstants.LOCATION_WORKSPACE.equalsIgnoreCase(location)) {
-			model = getBestCandidateModel(modelEntry.getWorkspaceModels(), version);
-		}
-		if (model == null) {
-			model = getBestCandidateModel(modelEntry.getExternalModels(), version);
-		}
-		if (model == null && IPDELauncherConstants.LOCATION_EXTERNAL.equalsIgnoreCase(location)) {
-			model = getBestCandidateModel(modelEntry.getWorkspaceModels(), version);
-		}
-		return model;
-	}
+	private static Map<IFeature, String> getSelectedFeatures(ILaunchConfiguration configuration, ITargetDefinition target) throws CoreException {
+		String featureLocation = configuration.getAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
 
-	private static boolean isSingleton(IPluginModelBase model) {
-		if (model.getBundleDescription() == null || model.getBundleDescription().isSingleton()) {
-			return true;
+		Predicate<IFeature> targetEnvironmentFilter = f -> f.matchesEnvironment(target);
+
+		// Get all available features
+		Map<String, List<List<IFeature>>> featureMaps = getPrioritizedAvailableFeatures(featureLocation);
+
+		Set<String> selectedFeatures = configuration.getAttribute(IPDELauncherConstants.SELECTED_FEATURES, emptySet());
+
+		Map<IFeature, String> feature2pluginResolution = new HashMap<>();
+		Queue<IFeature> pendingFeatures = new ArrayDeque<>();
+		for (String currentSelected : selectedFeatures) {
+			String[] attributes = currentSelected.split(FEATURE_PLUGIN_RESOLUTION_SEPARATOR);
+			if (attributes.length > 1) {
+				String id = attributes[0];
+				String pluginResolution = attributes[1];
+				IFeature feature = getRequiredFeature(id, null, IMatchRules.GREATER_OR_EQUAL, targetEnvironmentFilter, featureMaps);
+				addFeatureIfAbsent(feature, pluginResolution, feature2pluginResolution, pendingFeatures); // feature should be absent
+			}
 		}
-		return false;
-	}
 
-	/**
-	 * Returns model from the given list that is a 'best match' to the given bundle version or
-	 * <code>null</code> if no enabled models were in the provided list.  The best match will
-	 * be an exact version match if one is found.  Otherwise a model that is resolved in the
-	 * OSGi state with the highest version is returned.
-	 *
-	 * @param models list of candidate models to choose from
-	 * @param version the bundle version to find a match for
-	 * @return best candidate model from the list of models or <code>null</code> if no there were no acceptable models in the list
-	 */
-	private static IPluginModelBase getBestCandidateModel(IPluginModelBase[] models, String version) {
-		Version requiredVersion = version != null ? Version.parseVersion(version) : Version.emptyVersion;
-		IPluginModelBase model = null;
-		for (int i = 0; i < models.length; i++) {
-			if (models[i].getBundleDescription() == null || !models[i].isEnabled())
-				continue;
+		while (!pendingFeatures.isEmpty()) { // perform exhaustive breath-first-search for included and required features
+			IFeature feature = pendingFeatures.remove();
+			String pluginResolution = feature2pluginResolution.get(feature); // inherit resolution from including feature
 
-			if (model == null) {
-				model = models[i];
-				if (requiredVersion.compareTo(model.getBundleDescription().getVersion()) == 0) {
-					break;
+			IFeatureChild[] includedFeatures = feature.getIncludedFeatures();
+			for (IFeatureChild featureChild : includedFeatures) {
+				if (featureChild.matchesEnvironment(target)) {
+					IFeature child = getIncludedFeature(featureChild.getId(), featureChild.getVersion(), targetEnvironmentFilter, featureMaps);
+					addFeatureIfAbsent(child, pluginResolution, feature2pluginResolution, pendingFeatures);
 				}
-				continue;
 			}
 
-			if (!model.isEnabled() && models[i].isEnabled()) {
-				model = models[i];
-				continue;
-			}
-
-			BundleDescription current = model.getBundleDescription();
-			BundleDescription candidate = models[i].getBundleDescription();
-			if (current == null || candidate == null) {
-				continue;
-			}
-
-			if (!current.isResolved() && candidate.isResolved()) {
-				model = models[i];
-				continue;
-			}
-
-			if (requiredVersion.compareTo(candidate.getVersion()) == 0) {
-				model = models[i];
-				break;
-			}
-
-			if (current.getVersion().compareTo(candidate.getVersion()) < 0) {
-				model = models[i];
+			IFeatureImport[] featureImports = feature.getImports();
+			for (IFeatureImport featureImport : featureImports) {
+				if (featureImport.getType() == IFeatureImport.FEATURE) {
+					IFeature dependency = getRequiredFeature(featureImport.getId(), featureImport.getVersion(), featureImport.getMatch(), targetEnvironmentFilter, featureMaps);
+					addFeatureIfAbsent(dependency, pluginResolution, feature2pluginResolution, pendingFeatures);
+				}
 			}
 		}
-		return model;
+		return feature2pluginResolution;
 	}
 
-	public static IPluginModelBase[] getMergedBundles(ILaunchConfiguration configuration, boolean osgi) throws CoreException {
-		Map<IPluginModelBase, String> map = getMergedBundleMap(configuration, osgi);
-		return map.keySet().toArray(new IPluginModelBase[map.size()]);
+	private static Map<String, List<List<IFeature>>> getPrioritizedAvailableFeatures(String featureLocation) {
+		FeatureModelManager fmm = PDECore.getDefault().getFeatureModelManager();
+		List<IFeatureModel[]> featureModelsPerLocation = isWorkspace(featureLocation) //
+				? List.of(fmm.getWorkspaceModels(), fmm.getExternalModels()) //
+				: Collections.singletonList(fmm.getExternalModels());
+
+		Map<String, List<List<IFeature>>> featureMaps = new HashMap<>();
+		for (IFeatureModel[] featureModels : featureModelsPerLocation) {
+			Map<String, List<IFeature>> id2feature = Arrays.stream(featureModels).map(IFeatureModel::getFeature).collect(groupingBy(IFeature::getId));
+			id2feature.forEach((id, features) -> featureMaps.computeIfAbsent(id, i -> new ArrayList<>()).add(features));
+		}
+		return featureMaps;
 	}
 
-	public static Map<IPluginModelBase, String> getWorkspaceBundleMap(ILaunchConfiguration configuration, Set<String> pluginIds) throws CoreException {
+	private static void addFeatureIfAbsent(IFeature feature, String resolution, Map<IFeature, String> featurePluginResolution, Queue<IFeature> pendingFeatures) {
+		if (feature != null && featurePluginResolution.putIfAbsent(feature, resolution) == null) {
+			// Don't add feature more than once to not override the resolution if already present (e.g. a child was specified explicitly)
+			pendingFeatures.add(feature); // ... and to not process it more than once 
+		}
+	}
+
+	private static boolean isWorkspace(String location) {
+		if (IPDELauncherConstants.LOCATION_WORKSPACE.equalsIgnoreCase(location)) {
+			return true;
+		} else if (IPDELauncherConstants.LOCATION_EXTERNAL.equalsIgnoreCase(location)) {
+			return false;
+		}
+		throw new IllegalArgumentException("Unsupported location: " + location); //$NON-NLS-1$
+	}
+
+	private static final Comparator<IFeature> NEUTRAL_COMPARATOR = comparing(f -> 0);
+
+	private static IFeature getIncludedFeature(String id, String version, Predicate<IFeature> environmentFilter, Map<String, List<List<IFeature>>> prioritizedFeatures) {
+		List<List<IFeature>> features = prioritizedFeatures.get(id);
+		return getIncluded(features, environmentFilter, IFeature::getVersion, NEUTRAL_COMPARATOR, version);
+	}
+
+	private static IFeature getRequiredFeature(String id, String version, int versionMatchRule, Predicate<IFeature> environmentFilter, Map<String, List<List<IFeature>>> prioritizedFeatures) {
+		List<List<IFeature>> features = prioritizedFeatures.get(id);
+		return getRequired(features, environmentFilter, IFeature::getVersion, NEUTRAL_COMPARATOR, version, versionMatchRule);
+	}
+
+	private static final Predicate<IPluginModelBase> ENABLED_VALID_PLUGIN_FILTER = p -> p.getBundleDescription() != null && p.isEnabled();
+	private static final Function<IPluginModelBase, String> GET_PLUGIN_VERSION = m -> m.getPluginBase().getVersion();
+	private static final Comparator<IPluginModelBase> COMPARE_PLUGIN_RESOLVED = comparing(p -> p.getBundleDescription().isResolved());
+
+	private static IPluginModelBase getIncludedPlugin(String id, String version, String pluginLocation) {
+		List<List<IPluginModelBase>> plugins = getPlugins(id, pluginLocation);
+		return getIncluded(plugins, ENABLED_VALID_PLUGIN_FILTER, GET_PLUGIN_VERSION, COMPARE_PLUGIN_RESOLVED, version);
+	}
+
+	private static IPluginModelBase getRequiredPlugin(String id, String version, int versionMatchRule, String pluginLocation) {
+		List<List<IPluginModelBase>> plugins = getPlugins(id, pluginLocation);
+		return getRequired(plugins, ENABLED_VALID_PLUGIN_FILTER, GET_PLUGIN_VERSION, COMPARE_PLUGIN_RESOLVED, version, versionMatchRule);
+	}
+
+	private static List<List<IPluginModelBase>> getPlugins(String id, String pluginLocation) {
+		ModelEntry entry = PluginRegistry.findEntry(id);
+		if (entry == null) {
+			return Collections.emptyList();
+		}
+		List<IPluginModelBase> wsPlugins = List.of(entry.getWorkspaceModels()); // contains no or one element in most cases
+		List<IPluginModelBase> tpPlugins = List.of(entry.getExternalModels()); // contains no or one element in most cases
+		return isWorkspace(pluginLocation) ? List.of(wsPlugins, tpPlugins) : List.of(tpPlugins, wsPlugins);
+	}
+
+	/**
+	 * Selects and returns an {@code included} element for the specified version from the given containers using the following logic:
+	 * <p>
+	 * <ol>
+	 * <li>take first container</li>
+	 * <li>if an exactly qualified matching version exists select that</li>
+	 * <li>if an unqualified matching version exists select that</li>
+	 * <li>if any version exists, select the latest one</li>
+	 * <li>if no version was yet selected, go to next container and continue at step 2.</li>
+	 * </ol>
+	 * </p>
+	 * @return the selected included element or null if none was found
+	 */
+	private static <E> E getIncluded(List<List<E>> containers, Predicate<E> filter, Function<E, String> getVersion, Comparator<E> primaryComparator, String version) {
+		if (containers == null || containers.isEmpty()) {
+			return null;
+		}
+		Version includedVersion = Version.parseVersion(version);
+
+		Comparator<E> compareVersion = primaryComparator.thenComparing(e -> Version.parseVersion(getVersion.apply(e)), Comparator//
+				.<Version, Boolean> comparing(includedVersion::equals) // false < true
+				.thenComparing(v -> VersionUtil.compareMacroMinorMicro(v, includedVersion) == 0) // false < true
+				.thenComparing(Comparator.naturalOrder()));
+
+		return getMaxElement(containers, filter, compareVersion);
+	}
+
+	/**	
+	 * Selects and returns an {@code required} element for the specified version (may be null) and match-rule from the given containers using the following logic:
+	 * <p>
+	 * <ol>
+	 * <li>take first container</li>
+	 * <li>filter-out versions that do not obey the match rule with respect to the required version</li>
+	 * <li>selected and return latest version available</li>
+	 * <li>if no version was yet selected, go to next container and continue at step 2.</li>
+	 * </ol>
+	 * </p>
+	 * @return the selected required element or null if none was found
+	 */
+	private static <E> E getRequired(List<List<E>> containers, Predicate<E> filter, Function<E, String> getVersion, Comparator<E> primaryComparator, String version, int versionMatchRule) {
+		if (containers == null || containers.isEmpty()) {
+			return null;
+		}
+		if (version != null && !version.equals(Version.emptyVersion.toString())) {
+			Predicate<E> matchingVersion = e -> VersionUtil.compare(getVersion.apply(e), version, versionMatchRule);
+			filter = filter.and(matchingVersion);
+		} // if no/empty version is specified take the most recent version from the first/preferred location
+
+		Comparator<E> compareVersion = primaryComparator.thenComparing(e -> Version.parseVersion(getVersion.apply(e)));
+
+		return getMaxElement(containers, filter, compareVersion);
+	}
+
+	private static <E> E getMaxElement(List<List<E>> containers, Predicate<E> filter, Comparator<E> comparator) {
+		for (List<E> container : containers) {
+			Optional<E> selection = container.stream().filter(filter).max(comparator);
+			if (selection.isPresent()) { // take most recent element
+				return selection.get();
+			}
+		}
+		return null;
+	}
+
+	// --- plug-in based launches ---
+
+	private static final BiPredicate<List<Version>, Version> CONTAINS_SAME_VERSION = List::contains;
+	private static final BiPredicate<List<Version>, Version> CONTAINS_SAME_MMM_VERSION = (versions, toAdd) -> versions.stream().anyMatch(v -> VersionUtil.compareMacroMinorMicro(toAdd, v) == 0);
+
+	private static Map<IPluginModelBase, String> getWorkspaceBundleMap(ILaunchConfiguration configuration, Map<String, List<Version>> idVersions) throws CoreException {
 		Set<String> workspaceBundles = configuration.getAttribute(IPDELauncherConstants.SELECTED_WORKSPACE_BUNDLES, emptySet());
 
-		Map<IPluginModelBase, String> map = getBundleMap(workspaceBundles, ModelEntry::getWorkspaceModels, pluginIds, id -> true);
+		Map<IPluginModelBase, String> map = getBundleMap(workspaceBundles, ModelEntry::getWorkspaceModels, CONTAINS_SAME_VERSION, idVersions);
 
 		if (configuration.getAttribute(IPDELauncherConstants.AUTOMATIC_ADD, true)) {
 			Set<String> deselectedWorkspaceBundles = configuration.getAttribute(IPDELauncherConstants.DESELECTED_WORKSPACE_BUNDLES, emptySet());
-			Set<IPluginModelBase> deselectedPlugins = getBundleMap(deselectedWorkspaceBundles, ModelEntry::getWorkspaceModels, null, id -> true).keySet();
+			Set<IPluginModelBase> deselectedPlugins = getBundleMap(deselectedWorkspaceBundles, ModelEntry::getWorkspaceModels, null, null).keySet();
 			IPluginModelBase[] models = PluginRegistry.getWorkspaceModels();
 			for (IPluginModelBase model : models) {
-				String id = model.getPluginBase().getId();
-				if (id != null && !deselectedPlugins.contains(model)) {
-					if (pluginIds != null) {
-						pluginIds.add(id);
-					}
-					if (!map.containsKey(model)) {
-						addBundleToMap(map, model, "default:default"); //$NON-NLS-1$
-					}
+				if (model.getPluginBase().getId() != null && !deselectedPlugins.contains(model) && !map.containsKey(model)) {
+					addPlugin(map, model, DEFAULT_START_LEVELS, idVersions, CONTAINS_SAME_VERSION);
 				}
 			}
 		}
 		return map;
 	}
 
-	public static Map<IPluginModelBase, String> getTargetBundleMap(ILaunchConfiguration configuration, Set<String> pluginIds) throws CoreException {
+	private static Map<IPluginModelBase, String> getTargetBundleMap(ILaunchConfiguration configuration, Map<String, List<Version>> idVersions) throws CoreException {
 		Set<String> targetBundles = configuration.getAttribute(IPDELauncherConstants.SELECTED_TARGET_BUNDLES, emptySet());
-		Predicate<String> idFilter = pluginIds != null ? id -> !pluginIds.contains(id) : id -> true;
-		return getBundleMap(targetBundles, ModelEntry::getExternalModels, null, idFilter);
+		return getBundleMap(targetBundles, ModelEntry::getExternalModels, CONTAINS_SAME_MMM_VERSION, idVersions); // don't add same major-minor-micro-version more than once
 	}
 
-	private static Map<IPluginModelBase, String> getBundleMap(Set<String> entries, Function<ModelEntry, IPluginModelBase[]> getModels, Set<String> pluginIds, Predicate<String> idFilter) {
+	private static Map<IPluginModelBase, String> getBundleMap(Set<String> entries, Function<ModelEntry, IPluginModelBase[]> getModels, BiPredicate<List<Version>, Version> versionFilter, Map<String, List<Version>> idVersions) {
 		Map<IPluginModelBase, String> map = new LinkedHashMap<>();
 		for (String bundleEntry : entries) {
-			int index = bundleEntry.indexOf('@');
+			int index = bundleEntry.indexOf(START_LEVEL_SEPARATOR);
 			if (index < 0) { // if no start levels, assume default
 				index = bundleEntry.length();
-				bundleEntry += "@default:default"; //$NON-NLS-1$
+				bundleEntry += START_LEVEL_SEPARATOR + DEFAULT_START_LEVELS;
 			}
 			String idVersion = bundleEntry.substring(0, index);
 			int versionIndex = idVersion.indexOf(VERSION_SEPARATOR);
 			String id = (versionIndex > 0) ? idVersion.substring(0, versionIndex) : idVersion;
 			String version = (versionIndex > 0) ? idVersion.substring(versionIndex + 1) : null;
 
-			if (idFilter.test(id)) {
-				if (pluginIds != null) {
-					pluginIds.add(id);
-				}
-				ModelEntry entry = PluginRegistry.findEntry(id);
-				if (entry != null) {
-					IPluginModelBase[] models = getModels.apply(entry);
-					String startData = bundleEntry.substring(index + 1);
-					addPluginModel(models, version, startData, map);
+			ModelEntry entry = PluginRegistry.findEntry(id);
+			if (entry != null) {
+				IPluginModelBase[] models = getModels.apply(entry);
+				String startData = bundleEntry.substring(index + 1);
+				for (IPluginModelBase model : getSelectedModels(models, version, versionFilter == null)) {
+					addPlugin(map, model, startData, idVersions, versionFilter);
 				}
 			}
 		}
 		return map;
 	}
 
-	private static void addPluginModel(IPluginModelBase[] models, String version, String startData, Map<IPluginModelBase, String> map) {
-		Set<String> versions = new HashSet<>();
-		for (IPluginModelBase model : models) {
-			if (model.isEnabled()) { // always true for workspace models, external might be disabled
-				IPluginBase base = model.getPluginBase();
-				String v = base.getVersion();
-				if (versions.add(v)) { // don't add exact same version more than once
-					// match only if...
-					// a) if we have the same version
-					// b) no version
-					// c) all else fails, if there's just one bundle available, use it
-					if (base.getVersion().equals(version) || version == null || models.length == 1) {
-						addBundleToMap(map, model, startData);
-					}
-				}
+	static final Comparator<IPluginModelBase> VERSION = comparing(BundleLauncherHelper::getVersion);
+
+	private static Iterable<IPluginModelBase> getSelectedModels(IPluginModelBase[] models, String version, boolean greedy) {
+		// match only if...
+		// a) if we have the same version
+		// b) no version (if greedy take latest, else take all)
+		// c) all else fails, if there's just one bundle available, use it
+		Stream<IPluginModelBase> selectedModels = Arrays.stream(models).filter(IPluginModelBase::isEnabled); // workspace models are always enabled, external might be disabled
+		if (version == null) {
+			if (!greedy) {
+				IPluginModelBase latestModel = selectedModels.max(VERSION).orElseThrow();
+				selectedModels = Stream.of(latestModel); // take only  latest
+			} // Otherwise be greedy and take all if versionFilter is null
+		} else {
+			selectedModels = selectedModels.filter(m -> m.getPluginBase().getVersion().equals(version) || models.length == 1);
+		}
+		return selectedModels::iterator;
+	}
+
+	private static void addPlugin(Map<IPluginModelBase, String> map, IPluginModelBase model, String startData, Map<String, List<Version>> idVersions, BiPredicate<List<Version>, Version> containsVersion) {
+		if (containsVersion == null) { // be greedy and just take all (idVersions is null as well)
+			addBundleToMap(map, model, startData);
+		} else {
+			List<Version> pluginVersions = idVersions.computeIfAbsent(model.getPluginBase().getId(), n -> new ArrayList<>());
+			Version version = getVersion(model);
+			if (!containsVersion.test(pluginVersions, version)) { // apply version filter    
+				pluginVersions.add(version);
+				addBundleToMap(map, model, startData);
 			}
 		}
 	}
 
+	private static Version getVersion(IPluginModelBase model) {
+		BundleDescription bundleDescription = model.getBundleDescription();
+		if (bundleDescription == null) {
+			try {
+				return Version.parseVersion(model.getPluginBase().getVersion());
+			} catch (IllegalArgumentException e) {
+				return Version.emptyVersion;
+			}
+		}
+		return bundleDescription.getVersion();
+	}
+
 	/**
 	 * Adds the given bundle and start information to the map.  This will override anything set
 	 * for system bundles, and set their start level to the appropriate level
@@ -394,95 +447,72 @@
 	 * @param bundle The bundle to add
 	 * @param substring the start information in the form level:autostart
 	 */
-	private static void addBundleToMap(Map<IPluginModelBase, String> map, IPluginModelBase bundle, String sl) {
+	private static void addBundleToMap(Map<IPluginModelBase, String> map, IPluginModelBase bundle, String startData) {
 		BundleDescription desc = bundle.getBundleDescription();
-		boolean defaultsl = (sl == null || sl.equals("default:default")); //$NON-NLS-1$
+		boolean defaultsl = startData == null || startData.equals(DEFAULT_START_LEVELS);
 		if (desc != null && defaultsl) {
 			String runLevelText = resolveSystemRunLevelText(bundle);
 			String autoText = resolveSystemAutoText(bundle);
 			if (runLevelText != null && autoText != null) {
-				map.put(bundle, runLevelText + ":" + autoText); //$NON-NLS-1$
-			} else {
-				map.put(bundle, sl);
+				startData = runLevelText + AUTO_START_SEPARATOR + autoText;
 			}
-		} else {
-			map.put(bundle, sl);
 		}
+		map.put(bundle, startData);
 	}
 
+	private static final Map<String, String> AUTO_STARTED_BUNDLE_LEVELS = Map.ofEntries( //
+			Map.entry(IPDEBuildConstants.BUNDLE_DS, "1"), //$NON-NLS-1$
+			Map.entry(IPDEBuildConstants.BUNDLE_SIMPLE_CONFIGURATOR, "1"), //$NON-NLS-1$
+			Map.entry(IPDEBuildConstants.BUNDLE_EQUINOX_COMMON, "2"), //$NON-NLS-1$
+			Map.entry(IPDEBuildConstants.BUNDLE_OSGI, "1"), //$NON-NLS-1$
+			Map.entry(IPDEBuildConstants.BUNDLE_CORE_RUNTIME, DEFAULT), //
+			Map.entry(IPDEBuildConstants.BUNDLE_FELIX_SCR, "1")); //$NON-NLS-1$
+
 	public static String resolveSystemRunLevelText(IPluginModelBase model) {
 		BundleDescription description = model.getBundleDescription();
-		String modelName = description.getSymbolicName();
-
-		if (IPDEBuildConstants.BUNDLE_DS.equals(modelName)) {
-			return "1"; //$NON-NLS-1$
-		} else if (IPDEBuildConstants.BUNDLE_SIMPLE_CONFIGURATOR.equals(modelName)) {
-			return "1"; //$NON-NLS-1$
-		} else if (IPDEBuildConstants.BUNDLE_EQUINOX_COMMON.equals(modelName)) {
-			return "2"; //$NON-NLS-1$
-		} else if (IPDEBuildConstants.BUNDLE_OSGI.equals(modelName)) {
-			return "-1"; //$NON-NLS-1$
-		} else if (IPDEBuildConstants.BUNDLE_CORE_RUNTIME.equals(modelName)) {
-			if (TargetPlatformHelper.getTargetVersion() > 3.1) {
-				return "default"; //$NON-NLS-1$
-			}
-			return "2"; //$NON-NLS-1$
-		} else if (IPDEBuildConstants.BUNDLE_FELIX_SCR.equals(modelName)) {
-			return "1"; //$NON-NLS-1$
-		} else {
-			return null;
-		}
+		return AUTO_STARTED_BUNDLE_LEVELS.get(description.getSymbolicName());
 	}
 
 	public static String resolveSystemAutoText(IPluginModelBase model) {
 		BundleDescription description = model.getBundleDescription();
-		String modelName = description.getSymbolicName();
-
-		if (IPDEBuildConstants.BUNDLE_DS.equals(modelName)) {
-			return "true"; //$NON-NLS-1$
-		} else if (IPDEBuildConstants.BUNDLE_SIMPLE_CONFIGURATOR.equals(modelName)) {
-			return "true"; //$NON-NLS-1$
-		} else if (IPDEBuildConstants.BUNDLE_EQUINOX_COMMON.equals(modelName)) {
-			return "true"; //$NON-NLS-1$
-		} else if (IPDEBuildConstants.BUNDLE_OSGI.equals(modelName)) {
-			return "true"; //$NON-NLS-1$
-		} else if (IPDEBuildConstants.BUNDLE_CORE_RUNTIME.equals(modelName)) {
-			if (TargetPlatformHelper.getTargetVersion() > 3.1) {
-				return "true"; //$NON-NLS-1$
-			}
-			return "true"; //$NON-NLS-1$
-		} else if (IPDEBuildConstants.BUNDLE_FELIX_SCR.equals(modelName)) {
-			return "true"; //$NON-NLS-1$
-		} else {
-			return null;
-		}
+		return AUTO_STARTED_BUNDLE_LEVELS.containsKey(description.getSymbolicName()) ? "true" : null; //$NON-NLS-1$
 	}
 
-	public static String writeBundleEntry(IPluginModelBase model, String startLevel, String autoStart) {
+	public static String formatBundleEntry(IPluginModelBase model, String startLevel, String autoStart) {
 		IPluginBase base = model.getPluginBase();
 		String id = base.getId();
 		StringBuilder buffer = new StringBuilder(id);
 
 		ModelEntry entry = PluginRegistry.findEntry(id);
-		if (entry != null && entry.getActiveModels().length > 1) {
-			buffer.append(VERSION_SEPARATOR);
-			buffer.append(model.getPluginBase().getVersion());
+		if (entry != null) {
+			boolean isWorkspacePlugin = model.getUnderlyingResource() != null;
+			IPluginModelBase[] entryModels = isWorkspacePlugin ? entry.getWorkspaceModels() : entry.getExternalModels();
+			if (entryModels.length > 1) {
+				buffer.append(VERSION_SEPARATOR);
+				buffer.append(model.getPluginBase().getVersion());
+			}
 		}
 
-		boolean hasStartLevel = (startLevel != null && startLevel.length() > 0);
-		boolean hasAutoStart = (autoStart != null && autoStart.length() > 0);
+		boolean hasStartLevel = startLevel != null && !startLevel.isEmpty();
+		boolean hasAutoStart = autoStart != null && !autoStart.isEmpty();
 
-		if (hasStartLevel || hasAutoStart)
-			buffer.append('@');
-		if (hasStartLevel)
-			buffer.append(startLevel);
-		if (hasStartLevel || hasAutoStart)
-			buffer.append(':');
-		if (hasAutoStart)
-			buffer.append(autoStart);
+		if (hasStartLevel || hasAutoStart) {
+			buffer.append(START_LEVEL_SEPARATOR);
+			if (hasStartLevel) {
+				buffer.append(startLevel);
+			}
+			buffer.append(AUTO_START_SEPARATOR);
+			if (hasAutoStart) {
+				buffer.append(autoStart);
+			}
+		}
 		return buffer.toString();
 	}
 
+	public static String formatFeatureEntry(String featureId, String pluginResolution) {
+		return featureId + FEATURE_PLUGIN_RESOLUTION_SEPARATOR + pluginResolution;
+	}
+
 	@SuppressWarnings("deprecation")
 	public static void migrateLaunchConfiguration(ILaunchConfigurationWorkingCopy configuration) throws CoreException {
 
@@ -495,7 +525,7 @@
 				value = value.replace(':', ',');
 			}
 			value = (value.length() == 0 || value.equals(",")) //$NON-NLS-1$
-			? null
+					? null
 					: value.substring(0, value.length() - 1);
 
 			boolean automatic = configuration.getAttribute(IPDELauncherConstants.AUTOMATIC_ADD, true);
@@ -522,8 +552,9 @@
 		String version = configuration.getAttribute(IPDEConstants.LAUNCHER_PDE_VERSION, (String) null);
 		boolean newApp = TargetPlatformHelper.usesNewApplicationModel();
 		boolean upgrade = !"3.3".equals(version) && newApp; //$NON-NLS-1$
-		if (!upgrade)
+		if (!upgrade) {
 			upgrade = TargetPlatformHelper.getTargetVersion() >= 3.2 && version == null;
+		}
 		if (upgrade) {
 			configuration.setAttribute(IPDEConstants.LAUNCHER_PDE_VERSION, newApp ? "3.3" : "3.2a"); //$NON-NLS-1$ //$NON-NLS-2$
 			boolean usedefault = configuration.getAttribute(IPDELauncherConstants.USE_DEFAULT, true);
@@ -537,26 +568,30 @@
 					list.add("org.eclipse.equinox.preferences"); //$NON-NLS-1$
 					list.add("org.eclipse.equinox.registry"); //$NON-NLS-1$
 				}
-				if (!"3.3".equals(version) && newApp) //$NON-NLS-1$
+				if (!"3.3".equals(version) && newApp) { //$NON-NLS-1$
 					list.add("org.eclipse.equinox.app"); //$NON-NLS-1$
+				}
 				Set<String> extensions = new LinkedHashSet<>(configuration.getAttribute(IPDELauncherConstants.SELECTED_WORKSPACE_BUNDLES, emptySet()));
 				Set<String> target = new LinkedHashSet<>(configuration.getAttribute(IPDELauncherConstants.SELECTED_TARGET_BUNDLES, emptySet()));
 				for (String plugin : list) {
 					IPluginModelBase model = PluginRegistry.findModel(plugin);
-					if (model == null)
+					if (model == null) {
 						continue;
+					}
 					if (model.getUnderlyingResource() != null) {
-						if (automaticAdd)
-							continue;
-						extensions.add(plugin);
+						if (!automaticAdd) {
+							extensions.add(plugin);
+						}
 					} else {
 						target.add(plugin);
 					}
 				}
-				if (!extensions.isEmpty())
+				if (!extensions.isEmpty()) {
 					configuration.setAttribute(IPDELauncherConstants.SELECTED_WORKSPACE_BUNDLES, extensions);
-				if (!target.isEmpty())
+				}
+				if (!target.isEmpty()) {
 					configuration.setAttribute(IPDELauncherConstants.SELECTED_TARGET_BUNDLES, target);
+				}
 			}
 		}
 	}
@@ -578,7 +613,7 @@
 	private static void convertToSet(ILaunchConfigurationWorkingCopy wc, String stringAttribute, String listAttribute) throws CoreException {
 		String value = wc.getAttribute(stringAttribute, (String) null);
 		if (value != null) {
-			wc.setAttribute(stringAttribute, (String) null);
+			wc.removeAttribute(stringAttribute);
 			String[] itemArray = value.split(","); //$NON-NLS-1$
 			Set<String> itemSet = new HashSet<>(Arrays.asList(itemArray));
 			wc.setAttribute(listAttribute, itemSet);
@@ -600,37 +635,38 @@
 	 * @throws CoreException if there is a problem reading the launch config
 	 */
 	public static Map<IPluginModelBase, AdditionalPluginData> getAdditionalPlugins(ILaunchConfiguration config, boolean onlyEnabled) throws CoreException {
-		HashMap<IPluginModelBase, AdditionalPluginData> resolvedAdditionalPlugins = new HashMap<>();
-		Set<String> userAddedPlugins = config.getAttribute(IPDELauncherConstants.ADDITIONAL_PLUGINS, (Set<String>) null);
+		Map<IPluginModelBase, AdditionalPluginData> resolvedAdditionalPlugins = new HashMap<>();
+		Set<String> userAddedPlugins = config.getAttribute(IPDELauncherConstants.ADDITIONAL_PLUGINS, emptySet());
 		String defaultPluginResolution = config.getAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_WORKSPACE);
-		if (userAddedPlugins != null) {
-			for (String addedPlugin : userAddedPlugins) {
-				String[] pluginData = addedPlugin.split(":"); //$NON-NLS-1$
-				boolean checked = Boolean.parseBoolean(pluginData[3]);
-				if (!onlyEnabled || checked) {
-					String id = pluginData[0];
-					String version = pluginData[1];
-					String pluginResolution = pluginData[2];
-					ModelEntry pluginModelEntry = PluginRegistry.findEntry(id);
 
-					if (pluginModelEntry != null) {
-						if (IPDELauncherConstants.LOCATION_DEFAULT.equalsIgnoreCase(pluginResolution)) {
-							pluginResolution = defaultPluginResolution;
-						}
-						IPluginModelBase model = findModel(pluginModelEntry, version, pluginResolution);
-						if (model != null) {
-							String startLevel = (pluginData.length >= 6) ? pluginData[4] : null;
-							String autoStart = (pluginData.length >= 6) ? pluginData[5] : null;
-							AdditionalPluginData additionalPluginData = new AdditionalPluginData(pluginData[2], checked, startLevel, autoStart);
-							resolvedAdditionalPlugins.put(model, additionalPluginData);
-						}
-					}
+		for (String addedPlugin : userAddedPlugins) {
+			String[] pluginData = addedPlugin.split(FEATURES_ADDITIONAL_PLUGINS_DATA_SEPARATOR);
+			boolean checked = Boolean.parseBoolean(pluginData[3]);
+			if (!onlyEnabled || checked) {
+				String id = pluginData[0];
+				String version = pluginData[1];
+				String pluginResolution = pluginData[2];
+				if (IPDELauncherConstants.LOCATION_DEFAULT.equalsIgnoreCase(pluginResolution)) {
+					pluginResolution = defaultPluginResolution;
+				}
+
+				IPluginModelBase model = getIncludedPlugin(id, version, pluginResolution);
+				if (model != null) {
+					String startLevel = (pluginData.length >= 6) ? pluginData[4] : null;
+					String autoStart = (pluginData.length >= 6) ? pluginData[5] : null;
+					AdditionalPluginData additionalPluginData = new AdditionalPluginData(pluginData[2], checked, startLevel, autoStart);
+					resolvedAdditionalPlugins.put(model, additionalPluginData);
 				}
 			}
 		}
 		return resolvedAdditionalPlugins;
 	}
 
+	public static String formatAdditionalPluginEntry(IPluginModelBase pluginModel, String pluginResolution, boolean isChecked, String fStartLevel, String fAutoStart) {
+		IPluginBase plugin = pluginModel.getPluginBase();
+		return String.join(FEATURES_ADDITIONAL_PLUGINS_DATA_SEPARATOR, plugin.getId(), plugin.getVersion(), pluginResolution, String.valueOf(isChecked), fStartLevel, fAutoStart);
+	}
+
 	public static class AdditionalPluginData {
 		public final String fResolution;
 		public final boolean fEnabled;
@@ -640,12 +676,12 @@
 		public AdditionalPluginData(String resolution, boolean enabled, String startLevel, String autoStart) {
 			fResolution = resolution;
 			fEnabled = enabled;
-			fStartLevel = (startLevel == null || startLevel.isEmpty()) ? "default" : startLevel; //$NON-NLS-1$
-			fAutoStart = (autoStart == null || autoStart.isEmpty()) ? "default" : autoStart; //$NON-NLS-1$
+			fStartLevel = (startLevel == null || startLevel.isEmpty()) ? DEFAULT : startLevel;
+			fAutoStart = (autoStart == null || autoStart.isEmpty()) ? DEFAULT : autoStart;
 		}
 
 		String startLevels() {
-			return fStartLevel + ':' + fAutoStart;
+			return fStartLevel + AUTO_START_SEPARATOR + fAutoStart;
 		}
 	}
 }
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/EclipsePluginValidationOperation.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/EclipsePluginValidationOperation.java
index 30274f7..844350f 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/EclipsePluginValidationOperation.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/EclipsePluginValidationOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2015 IBM Corporation and others.
+ * Copyright (c) 2007, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -14,8 +14,7 @@
  *******************************************************************************/
 package org.eclipse.pde.internal.launching.launcher;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.debug.core.*;
 import org.eclipse.osgi.service.resolver.BundleDescription;
@@ -34,8 +33,8 @@
 	}
 
 	@Override
-	protected IPluginModelBase[] getModels() throws CoreException {
-		return BundleLauncherHelper.getMergedBundles(fLaunchConfiguration, false);
+	protected Set<IPluginModelBase> getModels() throws CoreException {
+		return BundleLauncherHelper.getMergedBundleMap(fLaunchConfiguration, false).keySet();
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchConfigurationHelper.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchConfigurationHelper.java
index 9a34d2b..044353f 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchConfigurationHelper.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchConfigurationHelper.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2017 IBM Corporation and others.
+ * Copyright (c) 2005, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -28,7 +28,6 @@
 import org.eclipse.pde.internal.build.IPDEBuildConstants;
 import org.eclipse.pde.internal.core.*;
 import org.eclipse.pde.internal.launching.IPDEConstants;
-import org.eclipse.pde.internal.launching.PDELaunchingPlugin;
 import org.eclipse.pde.launching.IPDELauncherConstants;
 
 /**
@@ -108,7 +107,7 @@
 	 * @return a properties object containing the properties written out to config.ini
 	 * @throws CoreException
 	 */
-	public static Properties createConfigIniFile(ILaunchConfiguration configuration, String productID, Map<String, IPluginModelBase> bundles, Map<IPluginModelBase, String> bundlesWithStartLevels, File configurationDirectory) throws CoreException {
+	public static Properties createConfigIniFile(ILaunchConfiguration configuration, String productID, Map<String, List<IPluginModelBase>> bundles, Map<IPluginModelBase, String> bundlesWithStartLevels, File configurationDirectory) throws CoreException {
 		Properties properties = null;
 		// if we are to generate a config.ini, start with the values in the target platform's config.ini - bug 141918
 		if (configuration.getAttribute(IPDELauncherConstants.CONFIG_GENERATE_DEFAULT, true)) {
@@ -194,7 +193,7 @@
 		return properties;
 	}
 
-	private static void addRequiredProperties(Properties properties, String productID, Map<String, IPluginModelBase> bundles, Map<IPluginModelBase, String> bundlesWithStartLevels) {
+	private static void addRequiredProperties(Properties properties, String productID, Map<String, List<IPluginModelBase>> bundles, Map<IPluginModelBase, String> bundlesWithStartLevels) {
 		if (!properties.containsKey("osgi.install.area")) //$NON-NLS-1$
 			properties.setProperty("osgi.install.area", "file:" + TargetPlatform.getLocation()); //$NON-NLS-1$ //$NON-NLS-2$
 		if (!properties.containsKey("osgi.configuration.cascaded")) //$NON-NLS-1$
@@ -222,7 +221,7 @@
 	 * @param bundlesWithStartLevels map of bundles of start level
 	 * @return string list of osgi bundles
 	 */
-	private static String computeOSGiBundles(String bundleList, Map<String, IPluginModelBase> bundles, Map<IPluginModelBase, String> bundlesWithStartLevels) {
+	private static String computeOSGiBundles(String bundleList, Map<String, List<IPluginModelBase>> bundles, Map<IPluginModelBase, String> bundlesWithStartLevels) {
 
 		// if p2 and only simple configurator and
 		// if simple configurator isn't selected & isn't in bundle list... hack it
@@ -277,13 +276,13 @@
 			} catch (Exception e) {
 				String message = e.getMessage();
 				if (message != null)
-					throw new CoreException(new Status(IStatus.ERROR, PDELaunchingPlugin.getPluginId(), IStatus.ERROR, message, e));
+					throw new CoreException(Status.error(message, e));
 			}
 		}
 		return properties;
 	}
 
-	private static void addSplashLocation(Properties properties, String productID, Map<String, IPluginModelBase> map) {
+	private static void addSplashLocation(Properties properties, String productID, Map<String, List<IPluginModelBase>> map) {
 		Properties targetConfig = TargetPlatformHelper.getConfigIniProperties();
 		String targetProduct = targetConfig == null ? null : targetConfig.getProperty("eclipse.product"); //$NON-NLS-1$
 		String targetSplash = targetConfig == null ? null : targetConfig.getProperty("osgi.splashPath"); //$NON-NLS-1$
@@ -291,7 +290,7 @@
 			ArrayList<String> locations = new ArrayList<>();
 			String plugin = getContributingPlugin(productID);
 			locations.add(plugin);
-			IPluginModelBase model = map.get(plugin);
+			IPluginModelBase model = getLatestModel(plugin, map);
 			if (model != null) {
 				BundleDescription desc = model.getBundleDescription();
 				if (desc != null) {
@@ -305,7 +304,7 @@
 			resolveLocationPath(targetSplash, properties, map);
 	}
 
-	private static void resolveLocationPath(String splashPath, Properties properties, Map<String, IPluginModelBase> map) {
+	private static void resolveLocationPath(String splashPath, Properties properties, Map<String, List<IPluginModelBase>> map) {
 		ArrayList<String> locations = new ArrayList<>();
 		StringTokenizer tok = new StringTokenizer(splashPath, ","); //$NON-NLS-1$
 		while (tok.hasMoreTokens())
@@ -313,7 +312,7 @@
 		resolveLocationPath(locations, properties, map);
 	}
 
-	private static void resolveLocationPath(ArrayList<String> locations, Properties properties, Map<String, IPluginModelBase> map) {
+	private static void resolveLocationPath(ArrayList<String> locations, Properties properties, Map<String, List<IPluginModelBase>> map) {
 		StringBuilder buffer = new StringBuilder();
 		for (int i = 0; i < locations.size(); i++) {
 			String location = locations.get(i);
@@ -339,11 +338,17 @@
 	 * @param includeReference whether to prefix the url with 'reference:'
 	 * @return string url for the bundle location
 	 */
-	public static String getBundleURL(String id, Map<String, IPluginModelBase> pluginMap, boolean includeReference) {
-		IPluginModelBase model = pluginMap.get(id.trim());
+	public static String getBundleURL(String id, Map<String, List<IPluginModelBase>> pluginMap, boolean includeReference) {
+		IPluginModelBase model = getLatestModel(id, pluginMap);
 		return getBundleURL(model, includeReference);
 	}
 
+
+	public static IPluginModelBase getLatestModel(String id, Map<String, List<IPluginModelBase>> plugins) {
+		List<IPluginModelBase> models = plugins.getOrDefault(id.trim(), Collections.emptyList());
+		return models.stream().max(BundleLauncherHelper.VERSION).orElse(null);
+	}
+
 	/**
 	 * Returns a string url representing the install location of the given bundle model
 	 * @param model the model to create the url for
@@ -368,7 +373,7 @@
 	 * @param map map of bundles being launched (id mapped to model)
 	 * @param properties properties for config.ini
 	 */
-	private static void setBundleLocations(Map<String, IPluginModelBase> map, Properties properties, boolean defaultAuto) {
+	private static void setBundleLocations(Map<String, List<IPluginModelBase>> map, Properties properties, boolean defaultAuto) {
 		String framework = properties.getProperty(PROP_OSGI_FRAMEWORK);
 		if (framework != null) {
 			framework = TargetPlatformHelper.stripPathInformation(framework);
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchListener.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchListener.java
index af440ee..e6e0e28 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchListener.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchListener.java
@@ -78,7 +78,7 @@
 			copy.setAttribute(IPDEConstants.RESTART, true);
 			copy.launch(launch.getLaunchMode(), new NullProgressMonitor());
 		} catch (CoreException e) {
-			Status status = new Status(IStatus.ERROR, IPDEConstants.PLUGIN_ID, 42, null, e);
+			IStatus status = Status.error(null, e);
 			IStatusHandler statusHandler = DebugPlugin.getDefault().getStatusHandler(status);
 			if (statusHandler == null)
 				PDELaunchingPlugin.log(e);
@@ -139,7 +139,7 @@
 			}
 			// launch failed for reasons printed to the log.
 			if (returnValue == 13) {
-				Status status = new Status(IStatus.ERROR, IPDEConstants.PLUGIN_ID, returnValue, PDEMessages.Launcher_error_code13, null);
+				IStatus status = Status.error(PDEMessages.Launcher_error_code13);
 				IStatusHandler statusHandler = DebugPlugin.getDefault().getStatusHandler(status);
 				if (statusHandler == null)
 					PDELaunchingPlugin.log(status);
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchPluginValidator.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchPluginValidator.java
index 0df5b78..38943ed 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchPluginValidator.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchPluginValidator.java
@@ -39,7 +39,7 @@
 			return models;
 
 		Collection<IPluginModelBase> result = null;
-		Map<IPluginModelBase, String> bundles = BundleLauncherHelper.getWorkspaceBundleMap(configuration, null);
+		Map<IPluginModelBase, String> bundles = BundleLauncherHelper.getWorkspaceBundleMap(configuration);
 		result = bundles.keySet();
 		return result.toArray(new IPluginModelBase[result.size()]);
 	}
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchValidationOperation.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchValidationOperation.java
index 44b7664..4fc259e 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchValidationOperation.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LaunchValidationOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2007, 2018 IBM Corporation and others.
+ *  Copyright (c) 2007, 2022 IBM Corporation and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -51,7 +51,7 @@
 		fOperation.run(monitor);
 	}
 
-	protected abstract IPluginModelBase[] getModels() throws CoreException;
+	protected abstract Set<IPluginModelBase> getModels() throws CoreException;
 
 	@SuppressWarnings("rawtypes")
 	protected Dictionary[] getPlatformProperties() throws CoreException {
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LauncherUtils.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LauncherUtils.java
index 587bf7a..4a9ecd2 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LauncherUtils.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/LauncherUtils.java
@@ -243,7 +243,7 @@
 	}
 
 	private static void handleSelectedPlugins(ILaunchConfiguration config, String timeStamp, ArrayList<IProject> projects) throws CoreException {
-		Map<IPluginModelBase, String> selectedPlugins = BundleLauncherHelper.getWorkspaceBundleMap(config, null);
+		Map<IPluginModelBase, String> selectedPlugins = BundleLauncherHelper.getWorkspaceBundleMap(config);
 		Iterator<IPluginModelBase> it = selectedPlugins.keySet().iterator();
 		while (it.hasNext()) {
 			IPluginModelBase model = it.next();
@@ -258,7 +258,7 @@
 	}
 
 	private static void handleDeselectedPlugins(ILaunchConfiguration config, String launcherTimeStamp, ArrayList<IProject> projects) throws CoreException {
-		Map<IPluginModelBase, String> deSelectedPlugins = BundleLauncherHelper.getWorkspaceBundleMap(config, null);
+		Map<IPluginModelBase, String> deSelectedPlugins = BundleLauncherHelper.getWorkspaceBundleMap(config);
 		IProject[] projs = ResourcesPlugin.getWorkspace().getRoot().getProjects();
 		for (int i = 0; i < projs.length; i++) {
 			if (!WorkspaceModelManager.isPluginProject(projs[i]))
@@ -341,10 +341,6 @@
 		return true;
 	}
 
-	public static IStatus createErrorStatus(String message) {
-		return new Status(IStatus.ERROR, PDELaunchingPlugin.getPluginId(), IStatus.OK, message, null);
-	}
-
 	/**
 	 * Updates the stores launch mode.  This should be called on any PDE Eclipse launch.  The launch mode
 	 * is passed to the status handler so it can open the correct launch configuration dialog
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/OSGiValidationOperation.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/OSGiValidationOperation.java
index c883f12..6d2f4df 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/OSGiValidationOperation.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/OSGiValidationOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2015 IBM Corporation and others.
+ * Copyright (c) 2007, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -14,6 +14,7 @@
  *******************************************************************************/
 package org.eclipse.pde.internal.launching.launcher;
 
+import java.util.Set;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.pde.core.plugin.IPluginModelBase;
@@ -25,8 +26,8 @@
 	}
 
 	@Override
-	protected IPluginModelBase[] getModels() throws CoreException {
-		return BundleLauncherHelper.getMergedBundles(fLaunchConfiguration, true);
+	protected Set<IPluginModelBase> getModels() throws CoreException {
+		return BundleLauncherHelper.getMergedBundleMap(fLaunchConfiguration, true).keySet();
 	}
 
 }
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/ProductValidationOperation.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/ProductValidationOperation.java
index aaaec8a..5743621 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/ProductValidationOperation.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/ProductValidationOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2009, 2015 EclipseSource Corporation and others.
+ * Copyright (c) 2009, 2022 EclipseSource Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -14,8 +14,7 @@
  *******************************************************************************/
 package org.eclipse.pde.internal.launching.launcher;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.jdt.launching.IVMInstall;
 import org.eclipse.jdt.launching.JavaRuntime;
@@ -25,15 +24,15 @@
 
 public class ProductValidationOperation extends LaunchValidationOperation {
 
-	private IPluginModelBase[] fModels;
+	private Set<IPluginModelBase> fModels;
 
-	public ProductValidationOperation(IPluginModelBase[] models) {
+	public ProductValidationOperation(Set<IPluginModelBase> models) {
 		super(null);
 		fModels = models;
 	}
 
 	@Override
-	protected IPluginModelBase[] getModels() throws CoreException {
+	protected Set<IPluginModelBase> getModels() throws CoreException {
 		return fModels;
 	}
 
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/VMHelper.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/VMHelper.java
index 2ce74b8..2c38347 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/VMHelper.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/VMHelper.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2015 IBM Corporation and others.
+ * Copyright (c) 2005, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -55,7 +55,7 @@
 
 		// Iterate through all launch models
 		boolean isOSGiLaunch = configuration instanceof EquinoxLaunchConfiguration; // TODO Test this
-		IPluginModelBase[] plugins = BundleLauncherHelper.getMergedBundles(configuration, isOSGiLaunch);
+		Set<IPluginModelBase> plugins = BundleLauncherHelper.getMergedBundleMap(configuration, isOSGiLaunch).keySet();
 		for (IPluginModelBase plugin : plugins) {
 			if (validEEs.isEmpty()) {
 				break; // No valid EEs left, short circuit
@@ -164,9 +164,9 @@
 				String id = JavaRuntime.getExecutionEnvironmentId(jrePath);
 				if (id == null) {
 					String name = JavaRuntime.getVMInstallName(jrePath);
-					throw new CoreException(LauncherUtils.createErrorStatus(NLS.bind(PDEMessages.WorkbenchLauncherConfigurationDelegate_noJRE, name)));
+					throw new CoreException(Status.error(NLS.bind(PDEMessages.WorkbenchLauncherConfigurationDelegate_noJRE, name)));
 				}
-				throw new CoreException(LauncherUtils.createErrorStatus(NLS.bind(PDEMessages.VMHelper_cannotFindExecEnv, id)));
+				throw new CoreException(Status.error(NLS.bind(PDEMessages.VMHelper_cannotFindExecEnv, id)));
 			}
 			return vm;
 		}
@@ -192,7 +192,7 @@
 		}
 
 		// No valid vm available, throw exception
-		throw new CoreException(LauncherUtils.createErrorStatus(NLS.bind(PDEMessages.WorkbenchLauncherConfigurationDelegate_noJRE, defaultVMName)));
+		throw new CoreException(Status.error(NLS.bind(PDEMessages.WorkbenchLauncherConfigurationDelegate_noJRE, defaultVMName)));
 
 	}
 
@@ -210,7 +210,7 @@
 	public static IVMInstall createLauncher(ILaunchConfiguration configuration) throws CoreException {
 		IVMInstall launcher = getVMInstall(configuration);
 		if (!launcher.getInstallLocation().exists())
-			throw new CoreException(LauncherUtils.createErrorStatus(PDEMessages.WorkbenchLauncherConfigurationDelegate_jrePathNotFound));
+			throw new CoreException(Status.error(PDEMessages.WorkbenchLauncherConfigurationDelegate_jrePathNotFound));
 		return launcher;
 	}
 
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/AbstractPDELaunchConfiguration.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/AbstractPDELaunchConfiguration.java
index 015a1ad..ec57649 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/AbstractPDELaunchConfiguration.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/AbstractPDELaunchConfiguration.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2005, 2018, 2020 IBM Corporation and others.
+ *  Copyright (c) 2005, 2018, 2022 IBM Corporation and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -22,6 +22,7 @@
 import org.eclipse.debug.core.*;
 import org.eclipse.debug.core.model.LaunchConfigurationDelegate;
 import org.eclipse.jdt.core.IJavaModelMarker;
+import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.launching.*;
 import org.eclipse.pde.core.plugin.TargetPlatform;
 import org.eclipse.pde.internal.core.ICoreConstants;
@@ -82,7 +83,7 @@
 			VMRunnerConfiguration runnerConfig = new VMRunnerConfiguration(getMainClass(), getClasspath(configuration));
 			IVMInstall launcher = VMHelper.createLauncher(configuration);
 			boolean isModular = JavaRuntime.isModularJava(launcher);
-			runnerConfig.setVMArguments(updateVMArgumentWithAddModuleSystem(getVMArguments(configuration), isModular));
+			runnerConfig.setVMArguments(updateVMArgumentWithAdditionalArguments(getVMArguments(configuration), isModular, configuration));
 			runnerConfig.setProgramArguments(getProgramArguments(configuration));
 			runnerConfig.setWorkingDirectory(getWorkingDirectory(configuration).getAbsolutePath());
 			runnerConfig.setEnvironment(getEnvironment(configuration));
@@ -122,7 +123,7 @@
 			VMRunnerConfiguration runnerConfig = new VMRunnerConfiguration(getMainClass(), getClasspath(configuration));
 			IVMInstall launcher = VMHelper.createLauncher(configuration);
 			boolean isModular = JavaRuntime.isModularJava(launcher);
-			runnerConfig.setVMArguments(updateVMArgumentWithAddModuleSystem(getVMArguments(configuration), isModular));
+			runnerConfig.setVMArguments(updateVMArgumentWithAdditionalArguments(getVMArguments(configuration), isModular, configuration));
 			runnerConfig.setProgramArguments(getProgramArguments(configuration));
 			runnerConfig.setWorkingDirectory(getWorkingDirectory(configuration).getAbsolutePath());
 			runnerConfig.setEnvironment(getEnvironment(configuration));
@@ -143,11 +144,40 @@
 		}
 	}
 
-	private String[] updateVMArgumentWithAddModuleSystem(String[] args, boolean isModular) {
+	private String[] updateVMArgumentWithAdditionalArguments(String[] args, boolean isModular, ILaunchConfiguration configuration) {
 		String modAllSystem= "--add-modules=ALL-SYSTEM"; //$NON-NLS-1$
-		if (isModular && !argumentContainsModuleSystem(args, modAllSystem)) {
-			args = Arrays.copyOf(args, args.length + 1);
-			args[args.length - 1] = modAllSystem;
+		String allowSecurityManager = "-Djava.security.manager=allow"; //$NON-NLS-1$
+		boolean addModuleSystem = false;
+		boolean addAllowSecurityManager = false;
+		int argLength = args.length;
+		if (isModular && !argumentContainsAttribute(args, modAllSystem)) {
+			addModuleSystem = true;
+			argLength++; // Need to add the argument
+		}
+		IVMInstall vmInstall;
+		try {
+			vmInstall = VMHelper.getVMInstall(configuration);
+			if (vmInstall instanceof AbstractVMInstall) {
+				AbstractVMInstall install = (AbstractVMInstall) vmInstall;
+				String vmver = install.getJavaVersion();
+				if (vmver != null && JavaCore.compareJavaVersions(vmver, JavaCore.VERSION_17) >= 0) {
+					if (!argumentContainsAttribute(args, allowSecurityManager)) {
+						addAllowSecurityManager = true;
+						argLength++; // Need to add the argument
+					}
+				}
+			}
+		} catch (CoreException e) {
+			PDELaunchingPlugin.log(e);
+		}
+		if (addModuleSystem || addAllowSecurityManager) {
+			args = Arrays.copyOf(args, argLength);
+			if (addAllowSecurityManager) {
+				args[--argLength] = allowSecurityManager;
+			}
+			if (addModuleSystem) {
+				args[--argLength] = modAllSystem;
+			}
 		}
 		if (!isModular) {
 			ArrayList<String> arrayList = new ArrayList<>(Arrays.asList(args));
@@ -158,7 +188,7 @@
 		return args;
 	}
 
-	private boolean argumentContainsModuleSystem(String[] args, String modAllSystem) {
+	private boolean argumentContainsAttribute(String[] args, String modAllSystem) {
 		for (String string : args) {
 			if (string.equals(modAllSystem))
 				return true;
@@ -219,7 +249,7 @@
 		String[] classpath = LaunchArgumentsHelper.constructClasspath(configuration);
 		if (classpath == null) {
 			String message = PDEMessages.WorkbenchLauncherConfigurationDelegate_noStartup;
-			throw new CoreException(LauncherUtils.createErrorStatus(message));
+			throw new CoreException(Status.error(message));
 		}
 		return classpath;
 	}
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/EclipseApplicationLaunchConfiguration.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/EclipseApplicationLaunchConfiguration.java
index 2566aca..5a1e102 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/EclipseApplicationLaunchConfiguration.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/EclipseApplicationLaunchConfiguration.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2015 IBM Corporation and others.
+ * Copyright (c) 2005, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -16,6 +16,8 @@
 
 import java.io.File;
 import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.variables.IStringVariableManager;
 import org.eclipse.core.variables.VariablesPlugin;
@@ -23,11 +25,10 @@
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.pde.core.plugin.IPluginModelBase;
 import org.eclipse.pde.core.plugin.TargetPlatform;
-import org.eclipse.pde.internal.core.*;
+import org.eclipse.pde.internal.core.ClasspathHelper;
+import org.eclipse.pde.internal.core.TargetPlatformHelper;
 import org.eclipse.pde.internal.core.util.CoreUtility;
-import org.eclipse.pde.internal.core.util.VersionUtil;
 import org.eclipse.pde.internal.launching.launcher.*;
-import org.osgi.framework.Version;
 
 /**
  * A launch delegate for launching Eclipse applications
@@ -43,8 +44,8 @@
 public class EclipseApplicationLaunchConfiguration extends AbstractPDELaunchConfiguration {
 
 	// used to generate the dev classpath entries
-	// key is bundle ID, value is a model
-	private Map<String, IPluginModelBase> fAllBundles;
+	// key is bundle ID, value is a List of models
+	private Map<String, List<IPluginModelBase>> fAllBundles;
 
 	// key is a model, value is startLevel:autoStart
 	private Map<IPluginModelBase, String> fModels;
@@ -95,11 +96,6 @@
 		// add the output folder names
 		programArgs.add("-dev"); //$NON-NLS-1$
 		programArgs.add(ClasspathHelper.getDevEntriesProperties(getConfigDir(configuration).toString() + "/dev.properties", fAllBundles)); //$NON-NLS-1$
-		// necessary for PDE to know how to load plugins when target platform = host platform
-		// see PluginPathFinder.getPluginPaths() and PluginPathFinder.isDevLaunchMode()
-		IPluginModelBase base = fAllBundles.get(PDECore.PLUGIN_ID);
-		if (base != null && VersionUtil.compareMacroMinorMicro(base.getBundleDescription().getVersion(), new Version("3.3.1")) < 0) //$NON-NLS-1$
-			programArgs.add("-pdelaunch"); //$NON-NLS-1$
 
 		String[] args = super.getProgramArguments(configuration);
 		Collections.addAll(programArgs, args);
@@ -181,12 +177,8 @@
 		fWorkspaceLocation = null;
 
 		fModels = BundleLauncherHelper.getMergedBundleMap(configuration, false);
-		fAllBundles = new HashMap<>(fModels.size());
-		Iterator<IPluginModelBase> iter = fModels.keySet().iterator();
-		while (iter.hasNext()) {
-			IPluginModelBase model = iter.next();
-			fAllBundles.put(model.getPluginBase().getId(), model);
-		}
+		fAllBundles = fModels.keySet().stream().collect(Collectors.groupingBy(m -> m.getPluginBase().getId()));
+
 		validateConfigIni(configuration);
 		super.preLaunchCheck(configuration, launch, monitor);
 	}
@@ -210,16 +202,9 @@
 	@Override
 	public String[] getVMArguments(ILaunchConfiguration configuration) throws CoreException {
 		String[] vmArgs = super.getVMArguments(configuration);
-		IPluginModelBase base = fAllBundles.get(PDECore.PLUGIN_ID);
-		if (base != null && VersionUtil.compareMacroMinorMicro(base.getBundleDescription().getVersion(), new Version("3.3.1")) >= 0) { //$NON-NLS-1$
-			// necessary for PDE to know how to load plugins when target platform = host platform
-			// see PluginPathFinder.getPluginPaths() and PluginPathFinder.isDevLaunchMode()
-			String[] result = new String[vmArgs.length + 1];
-			System.arraycopy(vmArgs, 0, result, 0, vmArgs.length);
-			result[vmArgs.length] = "-Declipse.pde.launch=true"; //$NON-NLS-1$
-			return result;
-		}
-		return vmArgs;
+		// necessary for PDE to know how to load plugins when target platform = host platform
+		// see PluginPathFinder.getPluginPaths() and PluginPathFinder.isDevLaunchMode()
+		return Stream.concat(Arrays.stream(vmArgs), Stream.of("-Declipse.pde.launch=true")).toArray(String[]::new); //$NON-NLS-1$
 	}
 
 }
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/EquinoxLaunchConfiguration.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/EquinoxLaunchConfiguration.java
index 1d20721..bff34e1 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/EquinoxLaunchConfiguration.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/EquinoxLaunchConfiguration.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2017 IBM Corporation and others.
+ * Copyright (c) 2005, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -18,6 +18,7 @@
 import java.net.URL;
 import java.util.*;
 import java.util.Map.Entry;
+import java.util.stream.Collectors;
 import org.eclipse.core.runtime.*;
 import org.eclipse.debug.core.ILaunch;
 import org.eclipse.debug.core.ILaunchConfiguration;
@@ -44,8 +45,8 @@
 public class EquinoxLaunchConfiguration extends AbstractPDELaunchConfiguration {
 
 	// used to generate the dev classpath entries
-	// key is bundle ID, value is a model
-	protected Map<String, IPluginModelBase> fAllBundles;
+	// key is bundle ID, value is a List of models
+	protected Map<String, List<IPluginModelBase>> fAllBundles;
 
 	// key is a model, value is startLevel:autoStart
 	private Map<IPluginModelBase, String> fModels;
@@ -84,7 +85,7 @@
 				properties.setProperty("org.eclipse.equinox.simpleconfigurator.configUrl", bundlesTxt.toString()); //$NON-NLS-1$
 			}
 			StringBuilder buffer = new StringBuilder();
-			IPluginModelBase model = fAllBundles.get(IPDEBuildConstants.BUNDLE_SIMPLE_CONFIGURATOR);
+			IPluginModelBase model = LaunchConfigurationHelper.getLatestModel(IPDEBuildConstants.BUNDLE_SIMPLE_CONFIGURATOR, fAllBundles);
 			buffer.append(LaunchConfigurationHelper.getBundleURL(model, true));
 			appendStartData(buffer, fModels.get(model), autostart);
 			bundles = buffer.toString();
@@ -152,22 +153,17 @@
 	@Override
 	protected void preLaunchCheck(ILaunchConfiguration configuration, ILaunch launch, IProgressMonitor monitor) throws CoreException {
 		fModels = BundleLauncherHelper.getMergedBundleMap(configuration, true);
-		fAllBundles = new HashMap<>(fModels.size());
-		Iterator<IPluginModelBase> iter = fModels.keySet().iterator();
-		while (iter.hasNext()) {
-			IPluginModelBase model = iter.next();
-			fAllBundles.put(model.getPluginBase().getId(), model);
-		}
+		fAllBundles = fModels.keySet().stream().collect(Collectors.groupingBy(m -> m.getPluginBase().getId(), HashMap::new, Collectors.toCollection(ArrayList::new)));
 
 		if (!fAllBundles.containsKey(IPDEBuildConstants.BUNDLE_OSGI)) {
 			// implicitly add it
 			IPluginModelBase model = PluginRegistry.findModel(IPDEBuildConstants.BUNDLE_OSGI);
 			if (model != null) {
 				fModels.put(model, "default:default"); //$NON-NLS-1$
-				fAllBundles.put(IPDEBuildConstants.BUNDLE_OSGI, model);
+				fAllBundles.computeIfAbsent(model.getPluginBase().getId(), i -> new ArrayList<>()).add(model);
 			} else {
 				String message = PDEMessages.EquinoxLaunchConfiguration_oldTarget;
-				throw new CoreException(LauncherUtils.createErrorStatus(message));
+				throw new CoreException(Status.error(message));
 			}
 		}
 		super.preLaunchCheck(configuration, launch, monitor);
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/JUnitLaunchConfigurationDelegate.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/JUnitLaunchConfigurationDelegate.java
index ccbf7b5..73a10db 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/JUnitLaunchConfigurationDelegate.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/JUnitLaunchConfigurationDelegate.java
@@ -19,6 +19,8 @@
 
 import java.io.File;
 import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.*;
 import org.eclipse.debug.core.*;
@@ -33,7 +35,6 @@
 import org.eclipse.pde.internal.core.util.VersionUtil;
 import org.eclipse.pde.internal.launching.*;
 import org.eclipse.pde.internal.launching.launcher.*;
-import org.osgi.framework.Version;
 
 /**
  * A launch delegate for launching JUnit Plug-in tests.
@@ -58,7 +59,7 @@
 
 	// used to generate the dev classpath entries
 	// key is bundle ID, value is a model
-	private Map<String, IPluginModelBase> fAllBundles;
+	private Map<String, List<IPluginModelBase>> fAllBundles;
 
 	// key is a model, value is startLevel:autoStart
 	private Map<IPluginModelBase, String> fModels;
@@ -78,15 +79,29 @@
 		return "org.eclipse.core.launcher.Main"; //$NON-NLS-1$
 	}
 
-	private String getTestPluginId(ILaunchConfiguration configuration) throws CoreException {
+	private IPluginBase getTestPlugin(ILaunchConfiguration configuration) throws CoreException {
 		IJavaProject javaProject = getJavaProject(configuration);
 		IPluginModelBase model = PluginRegistry.findModel(javaProject.getProject());
-		if (model == null)
+		if (model == null) {
 			abort(NLS.bind(PDEMessages.JUnitLaunchConfiguration_error_notaplugin, javaProject.getProject().getName()), null, IStatus.OK);
-		if (model instanceof IFragmentModel)
-			return ((IFragmentModel) model).getFragment().getPluginId();
+		}
+		if (model instanceof IFragmentModel) {
+			IFragment fragment = ((IFragmentModel) model).getFragment();
+			IPluginBase hostModel = getFragmentHostModel(fragment.getPluginId(), fragment.getPluginVersion(), fragment.getRule());
+			if (hostModel == null) {
+				abort(NLS.bind(PDEMessages.JUnitLaunchConfiguration_error_missingPlugin, fragment.getPluginId()), null, IStatus.OK);
+			}
+			model = hostModel.getPluginModel();
+		}
+		return model.getPluginBase();
+	}
 
-		return model.getPluginBase().getId();
+	private IPluginBase getFragmentHostModel(String hostId, String hostVersion, int hostVersionMatchRule) {
+		// return host plug-in model with matching version from bundles selected for launch 
+		List<IPluginModelBase> hosts = fAllBundles.getOrDefault(hostId, Collections.emptyList());
+		Stream<IPluginBase> hostPlugins = hosts.stream().map(IPluginModelBase::getPluginBase);
+		return hostPlugins.filter(h -> VersionUtil.compare(h.getVersion(), hostVersion, hostVersionMatchRule)) //
+				.max(Comparator.comparing(IPluginBase::getVersion)).orElse(null);
 	}
 
 	@Override
@@ -143,7 +158,7 @@
 
 		// Create the platform configuration for the runtime workbench
 		String productID = LaunchConfigurationHelper.getProductID(configuration);
-		String testPluginId = getTestPluginId(configuration);
+		IPluginBase testPlugin = getTestPlugin(configuration);
 		LaunchConfigurationHelper.createConfigIniFile(configuration, productID, fAllBundles, fModels, getConfigurationDirectory(configuration));
 		TargetPlatformHelper.checkPluginPropertiesConsistency(fAllBundles, getConfigurationDirectory(configuration));
 
@@ -162,7 +177,7 @@
 					.filter(IClasspathEntry::isTest)//
 					.filter(entry -> entry.getOutputLocation() != null).forEach(entry -> {
 						IPath relativePath = entry.getOutputLocation().removeFirstSegments(1).makeRelative();
-						devProperties.merge(testPluginId, relativePath.toString(), (vOld, vNew) -> vOld + "," + vNew); //$NON-NLS-1$
+						ClasspathHelper.addDevClasspath(testPlugin, devProperties, relativePath.toString(), true);
 					});
 		}
 		programArgs.add(ClasspathHelper.writeDevEntries(getConfigurationDirectory(configuration).toString() + "/dev.properties", devProperties)); //$NON-NLS-1$
@@ -200,7 +215,7 @@
 		}
 
 		programArgs.add("-testpluginname"); //$NON-NLS-1$
-		programArgs.add(testPluginId);
+		programArgs.add(testPlugin.getId());
 
 		IVMInstall launcher = VMHelper.createLauncher(configuration);
 		boolean isModular = JavaRuntime.isModularJava(launcher);
@@ -275,10 +290,7 @@
 		String vmArgs = LaunchArgumentsHelper.getUserVMArguments(configuration);
 
 		// necessary for PDE to know how to load plugins when target platform = host platform
-		IPluginModelBase base = fAllBundles.get(PDECore.PLUGIN_ID);
-		if (base != null && VersionUtil.compareMacroMinorMicro(base.getBundleDescription().getVersion(), new Version("3.3.1")) >= 0) { //$NON-NLS-1$
-			vmArgs = concatArg(vmArgs, "-Declipse.pde.launch=true"); //$NON-NLS-1$
-		}
+		vmArgs = concatArg(vmArgs, "-Declipse.pde.launch=true"); //$NON-NLS-1$
 		// For p2 target, add "-Declipse.p2.data.area=@config.dir/p2" unless already specified by user
 		if (fAllBundles.containsKey("org.eclipse.equinox.p2.core")) { //$NON-NLS-1$
 			if (!vmArgs.contains("-Declipse.p2.data.area=")) { //$NON-NLS-1$
@@ -394,12 +406,7 @@
 		fWorkspaceLocation = null;
 		fConfigDir = null;
 		fModels = BundleLauncherHelper.getMergedBundleMap(configuration, false);
-		fAllBundles = new LinkedHashMap<>(fModels.size());
-		Iterator<IPluginModelBase> iter = fModels.keySet().iterator();
-		while (iter.hasNext()) {
-			IPluginModelBase model = iter.next();
-			fAllBundles.put(model.getPluginBase().getId(), model);
-		}
+		fAllBundles = fModels.keySet().stream().collect(Collectors.groupingBy(m -> m.getPluginBase().getId(), LinkedHashMap::new, Collectors.toCollection(ArrayList::new)));
 
 		// implicitly add the plug-ins required for JUnit testing if necessary
 		String[] requiredPlugins = JUnitLaunchConfigurationDelegate.getRequiredPlugins(configuration);
@@ -407,7 +414,7 @@
 			String id = requiredPlugin;
 			if (!fAllBundles.containsKey(id)) {
 				IPluginModelBase model = findRequiredPluginInTargetOrHost(id);
-				fAllBundles.put(id, model);
+				fAllBundles.computeIfAbsent(model.getPluginBase().getId(), i -> new ArrayList<>()).add(model);
 				fModels.put(model, "default:default"); //$NON-NLS-1$
 			}
 		}
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/OSGiLaunchConfigurationDelegate.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/OSGiLaunchConfigurationDelegate.java
index 8c0e99b..889edf5 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/OSGiLaunchConfigurationDelegate.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/OSGiLaunchConfigurationDelegate.java
@@ -19,7 +19,8 @@
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.debug.core.model.LaunchConfigurationDelegate;
 import org.eclipse.osgi.util.NLS;
-import org.eclipse.pde.internal.launching.*;
+import org.eclipse.pde.internal.launching.PDELaunchingPlugin;
+import org.eclipse.pde.internal.launching.PDEMessages;
 import org.eclipse.pde.internal.launching.launcher.LaunchPluginValidator;
 import org.eclipse.pde.internal.launching.launcher.OSGiFrameworkManager;
 
@@ -54,8 +55,7 @@
 			if (name == null)
 				name = PDEMessages.OSGiLaunchConfiguration_selected;
 			String message = NLS.bind(PDEMessages.OSGiLaunchConfiguration_cannotFindLaunchConfiguration, name);
-			IStatus status = new Status(IStatus.ERROR, IPDEConstants.PLUGIN_ID, IStatus.OK, message, null);
-			throw new CoreException(status);
+			throw new CoreException(Status.error(message, null));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/OSGiLaunchConfigurationInitializer.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/OSGiLaunchConfigurationInitializer.java
index dbc2813..2a07356 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/OSGiLaunchConfigurationInitializer.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/OSGiLaunchConfigurationInitializer.java
@@ -102,7 +102,7 @@
 
 	private void appendBundle(Set<String> bundleSet, IPluginModelBase model) {
 		String id = model.getPluginBase().getId();
-		String value = BundleLauncherHelper.writeBundleEntry(model, getStartLevel(id), getAutoStart(id));
+		String value = BundleLauncherHelper.formatBundleEntry(model, getStartLevel(id), getAutoStart(id));
 		bundleSet.add(value);
 	}
 
diff --git a/ui/org.eclipse.pde.runtime/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.runtime/META-INF/MANIFEST.MF
index 3f4e580..06abbb8 100644
--- a/ui/org.eclipse.pde.runtime/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.runtime/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %name
 Bundle-SymbolicName: org.eclipse.pde.runtime; singleton:=true
-Bundle-Version: 3.7.200.qualifier
+Bundle-Version: 3.7.300.qualifier
 Bundle-Activator: org.eclipse.pde.internal.runtime.PDERuntimePlugin
 Bundle-Vendor: %provider-name
 Bundle-Localization: plugin
diff --git a/ui/org.eclipse.pde.runtime/pom.xml b/ui/org.eclipse.pde.runtime/pom.xml
index 28d70a3..2c1a796 100644
--- a/ui/org.eclipse.pde.runtime/pom.xml
+++ b/ui/org.eclipse.pde.runtime/pom.xml
@@ -14,13 +14,12 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.runtime</artifactId>
-  <version>3.7.200-SNAPSHOT</version>
+  <version>3.7.300-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 
   <properties>
diff --git a/ui/org.eclipse.pde.runtime/src/org/eclipse/pde/internal/runtime/PDERuntimePlugin.java b/ui/org.eclipse.pde.runtime/src/org/eclipse/pde/internal/runtime/PDERuntimePlugin.java
index 7037a17..8eb0a82 100644
--- a/ui/org.eclipse.pde.runtime/src/org/eclipse/pde/internal/runtime/PDERuntimePlugin.java
+++ b/ui/org.eclipse.pde.runtime/src/org/eclipse/pde/internal/runtime/PDERuntimePlugin.java
@@ -120,7 +120,7 @@
 		if (e instanceof CoreException) {
 			status = ((CoreException) e).getStatus();
 		} else if (e.getMessage() != null) {
-			status = new Status(IStatus.ERROR, ID, IStatus.OK, e.getMessage(), e);
+			status = Status.error(e.getMessage(), e);
 		}
 		if (status != null)
 			getDefault().getLog().log(status);
diff --git a/ui/org.eclipse.pde.runtime/src/org/eclipse/pde/internal/runtime/registry/model/LocalRegistryBackend.java b/ui/org.eclipse.pde.runtime/src/org/eclipse/pde/internal/runtime/registry/model/LocalRegistryBackend.java
index 208e18d..da9dd4d 100644
--- a/ui/org.eclipse.pde.runtime/src/org/eclipse/pde/internal/runtime/registry/model/LocalRegistryBackend.java
+++ b/ui/org.eclipse.pde.runtime/src/org/eclipse/pde/internal/runtime/registry/model/LocalRegistryBackend.java
@@ -86,13 +86,11 @@
 			if ((error.getType() & (ResolverError.MISSING_FRAGMENT_HOST | ResolverError.MISSING_GENERIC_CAPABILITY | ResolverError.MISSING_IMPORT_PACKAGE | ResolverError.MISSING_REQUIRE_BUNDLE)) != 0){
 				continue;
 			}
-			IStatus status = new Status(IStatus.WARNING, PDERuntimePlugin.ID, error.toString());
-			problems.add(status);
+			problems.add(Status.warning(error.toString()));
 		}
 
 		for (VersionConstraint constraint : unsatisfied) {
-			IStatus status = new Status(IStatus.WARNING, PDERuntimePlugin.ID, MessageHelper.getResolutionFailureMessage(constraint));
-			problems.add(status);
+			problems.add(Status.warning(MessageHelper.getResolutionFailureMessage(constraint)));
 		}
 
 		return problems;
diff --git a/ui/org.eclipse.pde.spy.bundle/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.spy.bundle/META-INF/MANIFEST.MF
index c417cb0..54f2de5 100644
--- a/ui/org.eclipse.pde.spy.bundle/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.spy.bundle/META-INF/MANIFEST.MF
@@ -7,12 +7,9 @@
 Automatic-Module-Name: org.eclipse.pde.spy.bundle
 Require-Bundle: org.eclipse.core.runtime;bundle-version="3.10.0",
  org.eclipse.jface;bundle-version="3.10.1",
- org.eclipse.e4.ui.model.workbench,
  org.eclipse.e4.core.contexts,
- org.eclipse.e4.ui.di,
  org.eclipse.e4.core.di,
- org.eclipse.e4.core.services,
- org.eclipse.pde.spy.core;bundle-version="1.0.0"
+ org.eclipse.e4.ui.di
 Bundle-Localization: plugin
 Import-Package: javax.annotation;version="1.3.5",
  javax.inject;version="1.0.0"
diff --git a/ui/org.eclipse.pde.spy.bundle/pom.xml b/ui/org.eclipse.pde.spy.bundle/pom.xml
index ed10982..c2b96b6 100644
--- a/ui/org.eclipse.pde.spy.bundle/pom.xml
+++ b/ui/org.eclipse.pde.spy.bundle/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
@@ -22,8 +22,6 @@
     <skipAPIAnalysis>true</skipAPIAnalysis>
   </properties>
 
-
-  <groupId>org.pde.ui</groupId>
   <artifactId>org.eclipse.pde.spy.bundle</artifactId>
   <version>0.12.100-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/BundleSpyPart.java b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/BundleSpyPart.java
index cf808b9..e087de9 100644
--- a/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/BundleSpyPart.java
+++ b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/BundleSpyPart.java
@@ -59,15 +59,15 @@
  */
 public class BundleSpyPart {
 
-	private static final String ICON_REFRESH = "icons/refresh.png";
-	public static final String ICON_STATE_ACTIVE = "icons/state_active.png";
-	public static final String ICON_STATE_STARTING = "icons/state_starting.png";
-	public static final String ICON_STATE_STOPPING = "icons/state_stopping.png";
-	public static final String ICON_STATE_RESOLVED = "icons/state_resolved.png";
-	public static final String ICON_STATE_INSTALLED = "icons/state_installed.png";
-	public static final String ICON_STATE_UNINSTALLED = "icons/state_uninstalled.png";
-	public static final String ICON_START = "icons/start.png";
-	public static final String ICON_STOP = "icons/stop.png";
+	private static final String ICON_REFRESH = "icons/refresh.png"; //$NON-NLS-1$
+	public static final String ICON_STATE_ACTIVE = "icons/state_active.png"; //$NON-NLS-1$
+	public static final String ICON_STATE_STARTING = "icons/state_starting.png"; //$NON-NLS-1$
+	public static final String ICON_STATE_STOPPING = "icons/state_stopping.png"; //$NON-NLS-1$
+	public static final String ICON_STATE_RESOLVED = "icons/state_resolved.png"; //$NON-NLS-1$
+	public static final String ICON_STATE_INSTALLED = "icons/state_installed.png"; //$NON-NLS-1$
+	public static final String ICON_STATE_UNINSTALLED = "icons/state_uninstalled.png"; //$NON-NLS-1$
+	public static final String ICON_START = "icons/start.png"; //$NON-NLS-1$
+	public static final String ICON_STOP = "icons/stop.png"; //$NON-NLS-1$
 
 	private TableViewer bundlesTableViewer;
 
@@ -102,7 +102,7 @@
 
 		Button refreshButton = new Button(comp, SWT.FLAT);
 		refreshButton.setImage(imgReg.get(ICON_REFRESH));
-		refreshButton.setToolTipText("Refresh the contexts");
+		refreshButton.setToolTipText(Messages.BundleSpyPart_9);
 		refreshButton.addSelectionListener(new SelectionAdapter() {
 			@Override
 			public void widgetSelected(SelectionEvent e) {
@@ -112,9 +112,9 @@
 
 		filterText = new Text(comp, SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
 		GridDataFactory.fillDefaults().hint(200, SWT.DEFAULT).applyTo(filterText);
-		filterText.setMessage("Search data");
+		filterText.setMessage(Messages.BundleSpyPart_10);
 		filterText.setToolTipText(
-				"Highlight the bundles where the contained objects contains this string.\n" + "Case is ignored.");
+				Messages.BundleSpyPart_11);
 		if (lastFilterText != null)
 			filterText.setText(lastFilterText);
 		bundleFilter.setPattern(lastFilterText);
@@ -136,8 +136,8 @@
 		});
 
 		showOnlyFilteredElements = new Button(comp, SWT.CHECK);
-		showOnlyFilteredElements.setText("Show Only Filtered");
-		showOnlyFilteredElements.setToolTipText("Show only the filtered items in the bundle table ");
+		showOnlyFilteredElements.setText(Messages.BundleSpyPart_12);
+		showOnlyFilteredElements.setToolTipText(Messages.BundleSpyPart_13);
 		showOnlyFilteredElements.setEnabled((lastFilterText != null) && (lastFilterText.length() > 0));
 		showOnlyFilteredElements.setSelection(lastShowFiltered);
 		showOnlyFilteredElements.addSelectionListener(new SelectionAdapter() {
@@ -150,7 +150,7 @@
 
 		startButton = new Button(comp, SWT.FLAT);
 		startButton.setImage(imgReg.get(ICON_START));
-		startButton.setToolTipText("Start the selected bundles not yet started");
+		startButton.setToolTipText(Messages.BundleSpyPart_14);
 		startButton.setEnabled(false);
 		startButton.addSelectionListener(new SelectionAdapter() {
 			@Override
@@ -172,13 +172,13 @@
 
 		stopButton = new Button(comp, SWT.FLAT);
 		stopButton.setImage(imgReg.get(ICON_STOP));
-		stopButton.setToolTipText("Stop the selected bundles not yet stopped");
+		stopButton.setToolTipText(Messages.BundleSpyPart_15);
 		stopButton.setEnabled(false);
 		stopButton.addSelectionListener(new SelectionAdapter() {
 			@Override
 			public void widgetSelected(SelectionEvent e) {
-				if (MessageDialog.openConfirm(((Control) e.getSource()).getShell(), "Confirm Bundle Stop",
-						"Stopping a bundle may cause problems in your current application.\nUse this button only for your bundles under testing\n\nDo you confirm you want to stop the selected started bundle(s) ? ")) {
+				if (MessageDialog.openConfirm(((Control) e.getSource()).getShell(), Messages.BundleSpyPart_16,
+						Messages.BundleSpyPart_17)) {
 					IStructuredSelection sel = (IStructuredSelection) bundlesTableViewer.getSelection();
 					Iterator<?> iter = sel.iterator();
 					while (iter.hasNext()) {
@@ -205,9 +205,9 @@
 		cTable.setLayoutData(gd_cTable);
 
 		// Create the first column for bundle name
-		addColumn(bundlesTableViewer, 35, "State", BundleDataProvider.COL_STATE);
-		addColumn(bundlesTableViewer, 200, "Bundle Name", BundleDataProvider.COL_NAME);
-		addColumn(bundlesTableViewer, 200, "Version", BundleDataProvider.COL_VERSION);
+		addColumn(bundlesTableViewer, 35, Messages.BundleSpyPart_18, BundleDataProvider.COL_STATE);
+		addColumn(bundlesTableViewer, 200, Messages.BundleSpyPart_19, BundleDataProvider.COL_NAME);
+		addColumn(bundlesTableViewer, 200, Messages.BundleSpyPart_20, BundleDataProvider.COL_VERSION);
 
 		// Set input data and content provider (default ArrayContentProvider)
 		bundlesTableViewer.setContentProvider(ArrayContentProvider.getInstance());
diff --git a/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/Messages.java b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/Messages.java
new file mode 100644
index 0000000..e735cb1
--- /dev/null
+++ b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/Messages.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2022 OPCoach and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Olivier Prouvost <olivier.prouvost@opcoach.com> - initial API and implementation (bug #577962)
+ *******************************************************************************/
+package org.eclipse.pde.spy.bundle;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+	private static final String BUNDLE_NAME = Messages.class.getPackageName() + ".messages"; //$NON-NLS-1$
+	public static String BundleSpyPart_10;
+	public static String BundleSpyPart_11;
+	public static String BundleSpyPart_12;
+	public static String BundleSpyPart_13;
+	public static String BundleSpyPart_14;
+	public static String BundleSpyPart_15;
+	public static String BundleSpyPart_16;
+	public static String BundleSpyPart_17;
+	public static String BundleSpyPart_18;
+	public static String BundleSpyPart_19;
+	public static String BundleSpyPart_20;
+	public static String BundleSpyPart_9;
+	static {
+		// initialize resource bundle
+		NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+	}
+
+	private Messages() {
+	}
+}
diff --git a/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/BundleDataFilter.java b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/BundleDataFilter.java
index 28ece26..caeb557 100644
--- a/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/BundleDataFilter.java
+++ b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/BundleDataFilter.java
@@ -39,7 +39,7 @@
 	public String getBundleStrings(Bundle b, int nbColumn) {
 		StringBuilder sb = new StringBuilder();
 		for (int i = 0; i < nbColumn; i++)
-			sb.append(BundleDataProvider.getText(b, i)).append("  ");
+			sb.append(BundleDataProvider.getText(b, i)).append("  "); //$NON-NLS-1$
 
 		return sb.toString();
 	}
diff --git a/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/BundleDataProvider.java b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/BundleDataProvider.java
index 0c901a8..d48616c 100644
--- a/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/BundleDataProvider.java
+++ b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/BundleDataProvider.java
@@ -70,7 +70,7 @@
 		case COL_VERSION:
 			return b.getVersion().toString();
 		case COL_STATE:
-			return ""; // No text for state (see tooltip)
+			return ""; // No text for state (see tooltip) //$NON-NLS-1$
 
 		}
 		return null;
@@ -117,21 +117,21 @@
 
 		switch (b.getState()) {
 		case Bundle.ACTIVE:
-			return "This bundle is Active";
+			return Messages.BundleDataProvider_1;
 		case Bundle.INSTALLED:
-			return "This bundle is Installed";
+			return Messages.BundleDataProvider_2;
 		case Bundle.RESOLVED:
-			return "This bundle is Resolved";
+			return Messages.BundleDataProvider_3;
 		case Bundle.STARTING:
-			return "This bundle is Starting";
+			return Messages.BundleDataProvider_4;
 		case Bundle.STOPPING:
-			return "This bundle is Stopping";
+			return Messages.BundleDataProvider_5;
 		case Bundle.UNINSTALLED:
-			return "This bundle is Uninstalled";
+			return Messages.BundleDataProvider_6;
 
 		}
 
-		return "This bundle is in state : " + b.getState();
+		return Messages.BundleDataProvider_7 + b.getState();
 
 	}
 
diff --git a/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/Messages.java b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/Messages.java
new file mode 100644
index 0000000..fe07a71
--- /dev/null
+++ b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/Messages.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2022 OPCoach and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Olivier Prouvost <olivier.prouvost@opcoach.com> - initial API and implementation (bug #577962)
+ *******************************************************************************/
+package org.eclipse.pde.spy.bundle.internal;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+	private static final String BUNDLE_NAME = Messages.class.getPackageName() + ".messages"; //$NON-NLS-1$
+	public static String BundleDataProvider_1;
+	public static String BundleDataProvider_2;
+	public static String BundleDataProvider_3;
+	public static String BundleDataProvider_4;
+	public static String BundleDataProvider_5;
+	public static String BundleDataProvider_6;
+	public static String BundleDataProvider_7;
+	static {
+		// initialize resource bundle
+		NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+	}
+
+	private Messages() {
+	}
+}
diff --git a/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/messages.properties b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/messages.properties
new file mode 100644
index 0000000..7c2090c
--- /dev/null
+++ b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/internal/messages.properties
@@ -0,0 +1,20 @@
+###############################################################################
+# Copyright (c) 2022 OPCoach and others.
+#
+#  This program and the accompanying materials
+#  are made available under the terms of the Eclipse Public License 2.0
+#  which accompanies this distribution, and is available at
+#  https://www.eclipse.org/legal/epl-2.0/
+#
+#  SPDX-License-Identifier: EPL-2.0
+# 
+#  Contributors:
+#     Olivier Prouvost <olivier.prouvost@opcoach.com> - initial API and implementation (bug #577962)
+###############################################################################
+BundleDataProvider_1=This bundle is Active
+BundleDataProvider_2=This bundle is Installed
+BundleDataProvider_3=This bundle is Resolved
+BundleDataProvider_4=This bundle is Starting
+BundleDataProvider_5=This bundle is Stopping
+BundleDataProvider_6=This bundle is Uninstalled
+BundleDataProvider_7=This bundle is in state : 
diff --git a/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/messages.properties b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/messages.properties
new file mode 100644
index 0000000..4ffeb17
--- /dev/null
+++ b/ui/org.eclipse.pde.spy.bundle/src/org/eclipse/pde/spy/bundle/messages.properties
@@ -0,0 +1,25 @@
+###############################################################################
+# Copyright (c) 2022 OPCoach and others.
+#
+#  This program and the accompanying materials
+#  are made available under the terms of the Eclipse Public License 2.0
+#  which accompanies this distribution, and is available at
+#  https://www.eclipse.org/legal/epl-2.0/
+#
+#  SPDX-License-Identifier: EPL-2.0
+# 
+#  Contributors:
+#     Olivier Prouvost <olivier.prouvost@opcoach.com> - initial API and implementation (bug #577962)
+###############################################################################
+BundleSpyPart_10=Search data
+BundleSpyPart_11=Highlight the bundles where the contained objects contains this string.\nCase is ignored.
+BundleSpyPart_12=Show Only Filtered
+BundleSpyPart_13=Show only the filtered items in the bundle table 
+BundleSpyPart_14=Start the selected bundles not yet started
+BundleSpyPart_15=Stop the selected bundles not yet stopped
+BundleSpyPart_16=Confirm Bundle Stop
+BundleSpyPart_17=Stopping a bundle may cause problems in your current application.\nUse this button only for your bundles under testing\n\nDo you confirm you want to stop the selected started bundle(s) ? 
+BundleSpyPart_18=State
+BundleSpyPart_19=Bundle Name
+BundleSpyPart_20=Version
+BundleSpyPart_9=Refresh the contexts
diff --git a/ui/org.eclipse.pde.spy.context/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.spy.context/META-INF/MANIFEST.MF
index f65cb6c..9f25000 100644
--- a/ui/org.eclipse.pde.spy.context/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.spy.context/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %name
 Bundle-SymbolicName: org.eclipse.pde.spy.context;singleton:=true
-Bundle-Version: 1.0.100.qualifier
+Bundle-Version: 1.0.200.qualifier
 Bundle-Vendor: %provider-name
 Automatic-Module-Name: org.eclipse.pde.spy.context
 Bundle-RequiredExecutionEnvironment: JavaSE-11
@@ -14,9 +14,7 @@
  org.eclipse.e4.ui.di;bundle-version="1.0.0",
  org.eclipse.e4.ui.services;bundle-version="1.0.0",
  org.eclipse.e4.core.di;bundle-version="1.3.0",
- org.eclipse.e4.core.services;bundle-version="1.1.0",
- org.eclipse.e4.ui.workbench.swt,
- org.eclipse.pde.spy.core;bundle-version="1.0.0"
+ org.eclipse.e4.core.services;bundle-version="1.1.0"
 Bundle-ActivationPolicy: lazy
 Import-Package: javax.annotation;version="1.2.0",
  javax.inject;version="1.0.0"
diff --git a/ui/org.eclipse.pde.spy.context/pom.xml b/ui/org.eclipse.pde.spy.context/pom.xml
index d39cd00..92f6b9a 100644
--- a/ui/org.eclipse.pde.spy.context/pom.xml
+++ b/ui/org.eclipse.pde.spy.context/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
@@ -22,9 +22,7 @@
     <skipAPIAnalysis>true</skipAPIAnalysis>
   </properties>
 
-
-  <groupId>eclipse.pde.ui</groupId>
   <artifactId>org.eclipse.pde.spy.context</artifactId>
-  <version>1.0.100-SNAPSHOT</version>
+  <version>1.0.200-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataFilter.java b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataFilter.java
index 9f922be..db76b18 100644
--- a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataFilter.java
+++ b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataFilter.java
@@ -138,7 +138,7 @@
 			}
 
 		} else {
-			log.warn("Warning : the received EclipseContext has not the expected type. It is a : "
+			log.warn(Messages.ContextDataFilter_0
 					+ ctx.getClass().toString());
 		}
 
diff --git a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataPart.java b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataPart.java
index 868ca35..f0cf569 100644
--- a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataPart.java
+++ b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataPart.java
@@ -75,17 +75,17 @@
 		cTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 
 		// tv.setInput(a);
-		contextDataViewer.setInput("Foo"); // getElements starts alone
+		contextDataViewer.setInput("Foo"); // getElements starts alone //$NON-NLS-1$
 
 		// Add columns in the tree
 		// Create the first column for the key
 		TreeViewerColumn keyCol = new TreeViewerColumn(contextDataViewer, SWT.NONE);
 		keyCol.getColumn().setWidth(400);
-		keyCol.getColumn().setText("Key");
+		keyCol.getColumn().setText(Messages.ContextDataPart_1);
 		ContextDataProvider keyLabelProvider = ContextInjectionFactory.make(ContextDataProvider.class, ctx);
 		keyLabelProvider.setDisplayKey(true);
 		keyCol.setLabelProvider(keyLabelProvider);
-		keyCol.getColumn().setToolTipText("Key in context");
+		keyCol.getColumn().setToolTipText(Messages.ContextDataPart_2);
 		keyCol.getColumn().addSelectionListener(
 				getHeaderSelectionAdapter(contextDataViewer, keyCol.getColumn(), 0, keyLabelProvider));
 
@@ -95,7 +95,7 @@
 		// Create the second column for the value
 		TreeViewerColumn valueCol = new TreeViewerColumn(contextDataViewer, SWT.NONE);
 		valueCol.getColumn().setWidth(600);
-		valueCol.getColumn().setText("Value");
+		valueCol.getColumn().setText(Messages.ContextDataPart_3);
 		ContextDataProvider valueLabelProvider = ContextInjectionFactory.make(ContextDataProvider.class, ctx);
 		valueCol.setLabelProvider(dataProvider);
 		valueCol.getColumn().addSelectionListener(
@@ -169,8 +169,8 @@
 			// Now can compare the text from label provider.
 			String lp1 = labelProvider.getText(e1);
 			String lp2 = labelProvider.getText(e2);
-			String s1 = lp1 == null ? "" : lp1.toLowerCase();
-			String s2 = lp2 == null ? "" : lp2.toLowerCase();
+			String s1 = lp1 == null ? "" : lp1.toLowerCase(); //$NON-NLS-1$
+			String s2 = lp2 == null ? "" : lp2.toLowerCase(); //$NON-NLS-1$
 			int rc = s1.compareTo(s2);
 			// If descending order, flip the direction
 			return (direction == SWT.DOWN) ? -rc : rc;
diff --git a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataProvider.java b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataProvider.java
index 0913d26..672147e 100644
--- a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataProvider.java
+++ b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextDataProvider.java
@@ -49,26 +49,26 @@
 @SuppressWarnings("restriction")
 public class ContextDataProvider extends ColumnLabelProvider implements ITreeContentProvider {
 
-	private static final String NO_VALUE_COULD_BE_COMPUTED = "No value could be yet computed";
+	private static final String NO_VALUE_COULD_BE_COMPUTED = Messages.ContextDataProvider_0;
 	private static final Color COLOR_IF_FOUND = Display.getCurrent().getSystemColor(SWT.COLOR_BLUE);
 	private static final Color COLOR_IF_NOT_COMPUTED = Display.getCurrent().getSystemColor(SWT.COLOR_MAGENTA);
 	private static final Object[] EMPTY_RESULT = new Object[0];
-	static final String LOCAL_VALUE_NODE = "Local values managed  by this context";
-	static final String INHERITED_INJECTED_VALUE_NODE = "Inherited values injected or updated using this context";
+	static final String LOCAL_VALUE_NODE = Messages.ContextDataProvider_1;
+	static final String INHERITED_INJECTED_VALUE_NODE = Messages.ContextDataProvider_2;
 
-	private static final String NO_VALUES_FOUND = "No values found";
-	private static final String UPDATED_IN_CLASS = "Updated in class :";
-	private static final String INJECTED_IN_FIELD = "Injected in field :";
-	private static final String INJECTED_IN_METHOD = "Injected in method :";
+	private static final String NO_VALUES_FOUND = Messages.ContextDataProvider_3;
+	private static final String UPDATED_IN_CLASS = Messages.ContextDataProvider_4;
+	private static final String INJECTED_IN_FIELD = Messages.ContextDataProvider_5;
+	private static final String INJECTED_IN_METHOD = Messages.ContextDataProvider_6;
 
 	// Image keys constants
-	private static final String PUBLIC_METHOD_IMG_KEY = "icons/methpub_obj.png";
-	private static final String PUBLIC_FIELD_IMG_KEY = "icons/field_public_obj.png";
-	private static final String VALUE_IN_CONTEXT_IMG_KEY = "icons/valueincontext.png";
-	private static final String INHERITED_VARIABLE_IMG_KEY = "icons/inher_co.png";
-	private static final String LOCAL_VARIABLE_IMG_KEY = "icons/letter-l-icon.png";
-	private static final String CONTEXT_FUNCTION_IMG_KEY = "icons/contextfunction.png";
-	private static final String INJECT_IMG_KEY = "icons/annotation_obj.png";
+	private static final String PUBLIC_METHOD_IMG_KEY = "icons/methpub_obj.png"; //$NON-NLS-1$
+	private static final String PUBLIC_FIELD_IMG_KEY = "icons/field_public_obj.png"; //$NON-NLS-1$
+	private static final String VALUE_IN_CONTEXT_IMG_KEY = "icons/valueincontext.png"; //$NON-NLS-1$
+	private static final String INHERITED_VARIABLE_IMG_KEY = "icons/inher_co.png"; //$NON-NLS-1$
+	private static final String LOCAL_VARIABLE_IMG_KEY = "icons/letter-l-icon.png"; //$NON-NLS-1$
+	private static final String CONTEXT_FUNCTION_IMG_KEY = "icons/contextfunction.png"; //$NON-NLS-1$
+	private static final String INJECT_IMG_KEY = "icons/annotation_obj.png"; //$NON-NLS-1$
 
 	private ImageRegistry imgReg;
 
@@ -122,7 +122,7 @@
 				try {
 					cfValues.put(key, selectedContext.get(key));
 				} catch (Exception e) {
-					cfValues.put(key, NO_VALUE_COULD_BE_COMPUTED + " (Exception : " + e.getClass().getName() + ")");
+					cfValues.put(key, NO_VALUE_COULD_BE_COMPUTED + " (Exception : " + e.getClass().getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
 				}
 			result.addAll(cfValues.entrySet());
 			return result.toArray();
@@ -170,15 +170,15 @@
 		if (element instanceof Map.Entry) {
 			Map.Entry<String, Object> mapEntry = (Map.Entry<String, Object>) element;
 			Object o = displayKey ? mapEntry.getKey() : mapEntry.getValue();
-			return (o == null) ? "null" : o.toString();
+			return (o == null) ? Messages.ContextDataProvider_16 : o.toString();
 		} else if (element instanceof Computation) {
 			// For a computation : display field or method in key column and the
 			// value in value
 			String txt = super.getText(element);
 			if (displayKey) {
-				if (txt.contains("#"))
+				if (txt.contains("#")) //$NON-NLS-1$
 					return INJECTED_IN_METHOD;
-				else if (txt.contains("@"))
+				else if (txt.contains("@")) //$NON-NLS-1$
 					return UPDATED_IN_CLASS;
 				else
 					return INJECTED_IN_FIELD;
@@ -225,9 +225,9 @@
 			// value in value column
 			String txt = super.getText(element);
 
-			if (txt.contains("#"))
+			if (txt.contains("#")) //$NON-NLS-1$
 				return imgReg.get(PUBLIC_METHOD_IMG_KEY);
-			else if (txt.contains("@"))
+			else if (txt.contains("@")) //$NON-NLS-1$
 				return imgReg.get(CONTEXT_FUNCTION_IMG_KEY);
 			else
 				return imgReg.get(PUBLIC_FIELD_IMG_KEY);
@@ -250,23 +250,23 @@
 	@Override
 	public String getToolTipText(Object element) {
 		if (element == LOCAL_VALUE_NODE) {
-			return "This part contains  values set in this context and then injected here or in children\n\n"
-					+ "If the value is injected using this context, you can expand the node to see where\n\n"
-					+ "If the value is injected using a child context you can find it in the second part for this child ";
+			return Messages.ContextDataProvider_21
+					+ Messages.ContextDataProvider_22
+					+ Messages.ContextDataProvider_23;
 		} else if (element == INHERITED_INJECTED_VALUE_NODE) {
-			return "This part contains the values injected or updated using this context, but initialized in a parent context\n\n"
-					+ "Expand nodes to see where values are injected or updated";
+			return Messages.ContextDataProvider_24
+					+ Messages.ContextDataProvider_25;
 		} else if (isAContextKeyFunction(element)) {
 			String key = (String) ((Map.Entry<?, ?>) element).getKey();
 			String fname = selectedContext.localContextFunction().get(key).getClass().getCanonicalName();
 
-			return "This value is created by the Context Function : " + fname;
+			return Messages.ContextDataProvider_26 + fname;
 		} else {
 			if (hasChildren(element))
-				return "Expand this node to see where this value is injected or updated";
+				return Messages.ContextDataProvider_27;
 			else {
 				if (element instanceof Map.Entry)
-					return "This value is set here but not injected using this context (look in children context)";
+					return Messages.ContextDataProvider_28;
 			}
 
 		}
diff --git a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextSpyHelper.java b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextSpyHelper.java
index 32b4aed..819ae6b 100644
--- a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextSpyHelper.java
+++ b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/ContextSpyHelper.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013 OPCoach.
+ * Copyright (c) 2013, 2021 OPCoach.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -42,19 +42,13 @@
 		Collection<IEclipseContext> result = Collections.emptyList();
 		try {
 			// Must use introspection to get the weak hash map (no getter).
-			Field f = EclipseContextFactory.class.getDeclaredField("serviceContexts");
+			Field f = EclipseContextFactory.class.getDeclaredField("serviceContexts"); //$NON-NLS-1$
 			f.setAccessible(true);
 			@SuppressWarnings("unchecked")
 			Map<BundleContext, IEclipseContext> ctxs = (Map<BundleContext, IEclipseContext>) f.get(null);
 			result = ctxs.values();
 
-		} catch (SecurityException e) {
-			e.printStackTrace();
-		} catch (NoSuchFieldException e) {
-			e.printStackTrace();
-		} catch (IllegalArgumentException e) {
-			e.printStackTrace();
-		} catch (IllegalAccessException e) {
+		} catch (SecurityException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
 			e.printStackTrace();
 		}
 
diff --git a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/Messages.java b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/Messages.java
new file mode 100644
index 0000000..4cc11ed
--- /dev/null
+++ b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/Messages.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2022 OPCoach and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Olivier Prouvost <olivier.prouvost@opcoach.com> - initial API and implementation (bug #577963)
+ *******************************************************************************/
+package org.eclipse.pde.internal.spy.context;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+	private static final String BUNDLE_NAME = Messages.class.getPackageName() + ".messages"; //$NON-NLS-1$
+	public static String ContextDataFilter_0;
+	public static String ContextDataPart_1;
+	public static String ContextDataPart_2;
+	public static String ContextDataPart_3;
+	public static String ContextDataProvider_0;
+	public static String ContextDataProvider_1;
+	public static String ContextDataProvider_16;
+	public static String ContextDataProvider_2;
+	public static String ContextDataProvider_21;
+	public static String ContextDataProvider_22;
+	public static String ContextDataProvider_23;
+	public static String ContextDataProvider_24;
+	public static String ContextDataProvider_25;
+	public static String ContextDataProvider_26;
+	public static String ContextDataProvider_27;
+	public static String ContextDataProvider_28;
+	public static String ContextDataProvider_3;
+	public static String ContextDataProvider_4;
+	public static String ContextDataProvider_5;
+	public static String ContextDataProvider_6;
+	static {
+		// initialize resource bundle
+		NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+	}
+
+	private Messages() {
+	}
+}
diff --git a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/messages.properties b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/messages.properties
new file mode 100644
index 0000000..d22c6b3
--- /dev/null
+++ b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/internal/spy/context/messages.properties
@@ -0,0 +1,33 @@
+###############################################################################
+# Copyright (c) 2022 OPCoach and others.
+#
+#  This program and the accompanying materials
+#  are made available under the terms of the Eclipse Public License 2.0
+#  which accompanies this distribution, and is available at
+#  https://www.eclipse.org/legal/epl-2.0/
+#
+#  SPDX-License-Identifier: EPL-2.0
+# 
+#  Contributors:
+#     Olivier Prouvost <olivier.prouvost@opcoach.com> - initial API and implementation (bug #577963)
+###############################################################################
+ContextDataFilter_0=Warning : the received EclipseContext has not the expected type. It is a : 
+ContextDataPart_1=Key
+ContextDataPart_2=Key in context
+ContextDataPart_3=Value
+ContextDataProvider_0=No value could be yet computed
+ContextDataProvider_1=Local values managed  by this context
+ContextDataProvider_16=null
+ContextDataProvider_2=Inherited values injected or updated using this context
+ContextDataProvider_21=This part contains  values set in this context and then injected here or in children\n\n
+ContextDataProvider_22=If the value is injected using this context, you can expand the node to see where\n\n
+ContextDataProvider_23=If the value is injected using a child context you can find it in the second part for this child 
+ContextDataProvider_24=This part contains the values injected or updated using this context, but initialized in a parent context\n\n
+ContextDataProvider_25=Expand nodes to see where values are injected or updated
+ContextDataProvider_26=This value is created by the Context Function : 
+ContextDataProvider_27=Expand this node to see where this value is injected or updated
+ContextDataProvider_28=This value is set here but not injected using this context (look in children context)
+ContextDataProvider_3=No values found
+ContextDataProvider_4=Updated in class :
+ContextDataProvider_5=Injected in field :
+ContextDataProvider_6=Injected in method :
diff --git a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/spy/context/ContextSpyPart.java b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/spy/context/ContextSpyPart.java
index e07666e..62ef34d 100644
--- a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/spy/context/ContextSpyPart.java
+++ b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/spy/context/ContextSpyPart.java
@@ -54,12 +54,12 @@
  */
 public class ContextSpyPart {
 
-	private static final String ICON_COLLAPSEALL = "icons/collapseall.png";
-	private static final String ICON_EXPANDALL = "icons/expandall.png";
-	private static final String ICON_REFRESH = "icons/refresh.png";
+	private static final String ICON_COLLAPSEALL = "icons/collapseall.png"; //$NON-NLS-1$
+	private static final String ICON_EXPANDALL = "icons/expandall.png"; //$NON-NLS-1$
+	private static final String ICON_REFRESH = "icons/refresh.png"; //$NON-NLS-1$
 
 	// The ID for this part descriptor
-	static final String CONTEXT_SPY_VIEW_DESC = "org.eclipse.e4.tools.context.spy.view";
+	static final String CONTEXT_SPY_VIEW_DESC = "org.eclipse.e4.tools.context.spy.view"; //$NON-NLS-1$
 
 	private TreeViewer contextTreeViewer;
 
@@ -102,7 +102,7 @@
 
 		Button refreshButton = new Button(comp, SWT.FLAT);
 		refreshButton.setImage(imgReg.get(ICON_REFRESH));
-		refreshButton.setToolTipText("Refresh the contexts");
+		refreshButton.setToolTipText(Messages.ContextSpyPart_4);
 		refreshButton.addSelectionListener(new SelectionAdapter() {
 			@Override
 			public void widgetSelected(SelectionEvent e) {
@@ -113,7 +113,7 @@
 
 		Button expandAll = new Button(comp, SWT.FLAT);
 		expandAll.setImage(imgReg.get(ICON_EXPANDALL));
-		expandAll.setToolTipText("Expand context nodes");
+		expandAll.setToolTipText(Messages.ContextSpyPart_5);
 		expandAll.addSelectionListener(new SelectionAdapter() {
 			@Override
 			public void widgetSelected(SelectionEvent e) {
@@ -122,7 +122,7 @@
 		});
 		Button collapseAll = new Button(comp, SWT.FLAT);
 		collapseAll.setImage(imgReg.get(ICON_COLLAPSEALL));
-		collapseAll.setToolTipText("Collapse context nodes");
+		collapseAll.setToolTipText(Messages.ContextSpyPart_6);
 		collapseAll.addSelectionListener(new SelectionAdapter() {
 			@Override
 			public void widgetSelected(SelectionEvent e) {
@@ -133,9 +133,8 @@
 
 		filterText = new Text(comp, SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
 		GridDataFactory.fillDefaults().hint(200, SWT.DEFAULT).applyTo(filterText);
-		filterText.setMessage("Search data");
-		filterText.setToolTipText("Highlight the contexts where the contained objects contains this string pattern.\n"
-				+ "Case is ignored.");
+		filterText.setMessage(Messages.ContextSpyPart_7);
+		filterText.setToolTipText(Messages.ContextSpyPart_8);
 		if (lastFilterText != null)
 			filterText.setText(lastFilterText);
 		contextFilter.setPattern(lastFilterText);
@@ -158,8 +157,8 @@
 		});
 
 		showOnlyFilteredElements = new Button(comp, SWT.CHECK);
-		showOnlyFilteredElements.setText("Show Only Filtered");
-		showOnlyFilteredElements.setToolTipText("Show only the filtered items in the table view");
+		showOnlyFilteredElements.setText(Messages.ContextSpyPart_9);
+		showOnlyFilteredElements.setToolTipText(Messages.ContextSpyPart_10);
 		showOnlyFilteredElements.setEnabled((lastFilterText != null) && (lastFilterText.length() > 0));
 		showOnlyFilteredElements.setSelection(lastShowFiltered);
 		showOnlyFilteredElements.addSelectionListener(new SelectionAdapter() {
@@ -191,7 +190,7 @@
 			}
 		});
 
-		IEclipseContext subCtx = ctx.createChild("Context for ContextDataPart");
+		IEclipseContext subCtx = ctx.createChild(Messages.ContextSpyPart_11);
 		subCtx.set(Composite.class, sashForm);
 		contextDataPart = ContextInjectionFactory.make(ContextDataPart.class, subCtx);
 		setFilter();
diff --git a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/spy/context/Messages.java b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/spy/context/Messages.java
new file mode 100644
index 0000000..18525fb
--- /dev/null
+++ b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/spy/context/Messages.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2022 OPCoach and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Olivier Prouvost <olivier.prouvost@opcoach.com> - initial API and implementation (bug #577963)
+ *******************************************************************************/
+package org.eclipse.pde.spy.context;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+	private static final String BUNDLE_NAME = Messages.class.getPackageName() + ".messages"; //$NON-NLS-1$
+	public static String ContextSpyPart_10;
+	public static String ContextSpyPart_11;
+	public static String ContextSpyPart_4;
+	public static String ContextSpyPart_5;
+	public static String ContextSpyPart_6;
+	public static String ContextSpyPart_7;
+	public static String ContextSpyPart_8;
+	public static String ContextSpyPart_9;
+	static {
+		// initialize resource bundle
+		NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+	}
+
+	private Messages() {
+	}
+}
diff --git a/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/spy/context/messages.properties b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/spy/context/messages.properties
new file mode 100644
index 0000000..a305164
--- /dev/null
+++ b/ui/org.eclipse.pde.spy.context/src/org/eclipse/pde/spy/context/messages.properties
@@ -0,0 +1,21 @@
+###############################################################################
+# Copyright (c) 2022 OPCoach and others.
+#
+#  This program and the accompanying materials
+#  are made available under the terms of the Eclipse Public License 2.0
+#  which accompanies this distribution, and is available at
+#  https://www.eclipse.org/legal/epl-2.0/
+#
+#  SPDX-License-Identifier: EPL-2.0
+# 
+#  Contributors:
+#     Olivier Prouvost <olivier.prouvost@opcoach.com> - initial API and implementation (bug #577963)
+###############################################################################
+ContextSpyPart_10=Show only the filtered items in the table view
+ContextSpyPart_11=Context for ContextDataPart
+ContextSpyPart_4=Refresh the contexts
+ContextSpyPart_5=Expand context nodes
+ContextSpyPart_6=Collapse context nodes
+ContextSpyPart_7=Search data
+ContextSpyPart_8=Highlight the contexts where the contained objects contains this string pattern.\nCase is ignored.
+ContextSpyPart_9=Show Only Filtered
diff --git a/ui/org.eclipse.pde.spy.core/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.spy.core/META-INF/MANIFEST.MF
index 667bcd7..7309c73 100644
--- a/ui/org.eclipse.pde.spy.core/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.spy.core/META-INF/MANIFEST.MF
@@ -9,11 +9,7 @@
 Require-Bundle: org.eclipse.e4.ui.model.workbench,
  org.eclipse.core.runtime;bundle-version="3.9.0",
  org.eclipse.e4.core.di,
- org.eclipse.e4.core.services,
  org.eclipse.e4.ui.workbench,
- org.eclipse.e4.ui.services,
- org.eclipse.e4.core.contexts,
  org.eclipse.e4.ui.di
-Import-Package: javax.annotation;version="1.2.0",
- javax.inject;version="1.0.0"
+Import-Package: javax.inject;version="1.0.0"
 Bundle-Vendor: %provider-name
diff --git a/ui/org.eclipse.pde.spy.core/pom.xml b/ui/org.eclipse.pde.spy.core/pom.xml
index 0db1a99..1ec4fe7 100644
--- a/ui/org.eclipse.pde.spy.core/pom.xml
+++ b/ui/org.eclipse.pde.spy.core/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
@@ -22,8 +22,6 @@
     <skipAPIAnalysis>true</skipAPIAnalysis>
   </properties>
 
-
-  <groupId>org.pde.ui</groupId>
   <artifactId>org.eclipse.pde.spy.core</artifactId>
   <version>1.0.100-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ui/org.eclipse.pde.spy.css/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.spy.css/META-INF/MANIFEST.MF
index 6a33fae..6deb6cd 100644
--- a/ui/org.eclipse.pde.spy.css/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.spy.css/META-INF/MANIFEST.MF
@@ -2,29 +2,20 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %name
 Bundle-SymbolicName: org.eclipse.pde.spy.css;singleton:=true
-Bundle-Version: 0.12.100.qualifier
+Bundle-Version: 0.12.200.qualifier
 Automatic-Module-Name: org.eclipse.pde.spy.css
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Require-Bundle: org.eclipse.core.runtime;bundle-version="3.6.0",
- org.eclipse.e4.ui.workbench;bundle-version="0.9.0";resolution:=optional,
  org.eclipse.e4.ui.css.core;bundle-version="0.9.0",
- org.eclipse.swt;bundle-version="3.6.0",
  org.eclipse.jface;bundle-version="3.8.0",
  org.eclipse.e4.ui.css.swt;bundle-version="0.10.0",
  org.eclipse.e4.ui.css.swt.theme;bundle-version="0.9.201",
- org.eclipse.e4.ui.widgets;bundle-version="0.11.0",
- org.eclipse.e4.ui.model.workbench;bundle-version="0.9.1",
- org.eclipse.e4.ui.di,
- org.eclipse.e4.core.di.extensions,
- org.eclipse.pde.spy.core;bundle-version="1.0.0"
+ org.eclipse.e4.ui.model.workbench;bundle-version="0.9.1"
 Bundle-ActivationPolicy: lazy
 Import-Package: javax.annotation;version="1.2.0",
  javax.inject;version="1.0.0",
  org.eclipse.e4.core.contexts,
  org.eclipse.e4.core.di.annotations,
- org.eclipse.e4.core.services.log,
- org.eclipse.e4.core.services.statusreporter,
- org.eclipse.e4.ui.bindings,
  org.eclipse.e4.ui.services,
  org.w3c.css.sac;version="1.3.0"
 Bundle-Vendor: %provider-name
diff --git a/ui/org.eclipse.pde.spy.css/pom.xml b/ui/org.eclipse.pde.spy.css/pom.xml
index f475458..b809c0e 100644
--- a/ui/org.eclipse.pde.spy.css/pom.xml
+++ b/ui/org.eclipse.pde.spy.css/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
@@ -22,9 +22,7 @@
     <skipAPIAnalysis>true</skipAPIAnalysis>
   </properties>
 
-
-  <groupId>org.eclipse.e4</groupId>
   <artifactId>org.eclipse.pde.spy.css</artifactId>
-  <version>0.12.100-SNAPSHOT</version>
+  <version>0.12.200-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/ui/org.eclipse.pde.spy.css/src/org/eclipse/pde/spy/css/CssSpyPart.java b/ui/org.eclipse.pde.spy.css/src/org/eclipse/pde/spy/css/CssSpyPart.java
index 47d3da0..d76d046 100644
--- a/ui/org.eclipse.pde.spy.css/src/org/eclipse/pde/spy/css/CssSpyPart.java
+++ b/ui/org.eclipse.pde.spy.css/src/org/eclipse/pde/spy/css/CssSpyPart.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2011, 2016 Manumitting Technologies, Inc.
+ * Copyright (c) 2011, 2021 Manumitting Technologies, Inc.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -856,7 +856,7 @@
 		}
 		widgetTreeViewer.collapseAll();
 		Object[] roots = widgetTreeProvider.getElements(widgetTreeViewer.getInput());
-		SubMonitor subMonitor = SubMonitor.convert(monitor, MessageFormat.format(Messages.CssSpyPart_Searching_for, text), roots.length * 10); //$NON-NLS-2$
+		SubMonitor subMonitor = SubMonitor.convert(monitor, MessageFormat.format(Messages.CssSpyPart_Searching_for, text), roots.length * 10);
 		for (Object root : roots) {
 			if (monitor.isCanceled()) {
 				return;
diff --git a/ui/org.eclipse.pde.spy.model/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.spy.model/META-INF/MANIFEST.MF
index d5ba828..fae03ec 100644
--- a/ui/org.eclipse.pde.spy.model/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.spy.model/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %name
 Bundle-SymbolicName: org.eclipse.pde.spy.model;singleton:=true
-Bundle-Version: 0.12.100.qualifier
+Bundle-Version: 0.12.200.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Require-Bundle: org.eclipse.e4.ui.services;bundle-version="0.9.1",
  org.eclipse.e4.tools.emf.ui;bundle-version="4.7.0",
@@ -15,13 +15,11 @@
  org.eclipse.e4.core.di;bundle-version="0.9.0",
  org.eclipse.jface;bundle-version="3.6.0",
  org.eclipse.equinox.registry;bundle-version="3.5.0",
- org.eclipse.e4.ui.di;bundle-version="0.9.0",
- org.eclipse.pde.spy.core;bundle-version="1.0.0"
+ org.eclipse.e4.ui.di;bundle-version="0.9.0"
 Bundle-Localization: plugin
 Bundle-Vendor: %provider-name
 Service-Component: OSGI-INF/extensionlookup.xml
 Bundle-ActivationPolicy: lazy
-Import-Package: javax.annotation;version="1.2.0",
- javax.inject;version="1.0.0"
+Import-Package: javax.inject;version="1.0.0"
 Export-Package: org.eclipse.pde.spy.model;x-internal:=true
 Automatic-Module-Name: org.eclipse.pde.spy.model
diff --git a/ui/org.eclipse.pde.spy.model/pom.xml b/ui/org.eclipse.pde.spy.model/pom.xml
index 4fd55a2..5dd2f2e 100644
--- a/ui/org.eclipse.pde.spy.model/pom.xml
+++ b/ui/org.eclipse.pde.spy.model/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
@@ -22,9 +22,7 @@
     <skipAPIAnalysis>true</skipAPIAnalysis>
   </properties>
 
-
-  <groupId>org.eclipse.e4</groupId>
   <artifactId>org.eclipse.pde.spy.model</artifactId>
-  <version>0.12.100-SNAPSHOT</version>
+  <version>0.12.200-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/ui/org.eclipse.pde.spy.preferences/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.spy.preferences/META-INF/MANIFEST.MF
index 95c24d0..9663c86 100644
--- a/ui/org.eclipse.pde.spy.preferences/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.spy.preferences/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %name
 Bundle-SymbolicName: org.eclipse.pde.spy.preferences;singleton:=true
-Bundle-Version: 0.12.100.qualifier
+Bundle-Version: 0.12.200.qualifier
 Automatic-Module-Name: org.eclipse.pde.spy.preferences
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Bundle-Vendor: %provider-name
@@ -11,17 +11,14 @@
  org.eclipse.e4.core.di;bundle-version="1.4.0",
  org.eclipse.e4.core.di.extensions;bundle-version="0.12.0",
  org.eclipse.e4.core.services;bundle-version="1.2.0",
- org.eclipse.osgi.services;bundle-version="3.4.0",
  org.eclipse.e4.ui.di;bundle-version="1.0.0",
  org.eclipse.core.databinding;bundle-version="1.4.1",
  org.eclipse.core.databinding.beans;bundle-version="1.2.200",
- org.eclipse.core.databinding.observable;bundle-version="1.4.1",
  org.eclipse.core.databinding.property;bundle-version="1.4.200",
  org.eclipse.jface.databinding;bundle-version="1.6.200",
  org.eclipse.e4.ui.services;bundle-version="1.1.0",
  org.eclipse.e4.ui.workbench;bundle-version="1.1.0",
  org.eclipse.core.runtime,
- org.eclipse.pde.spy.core;bundle-version="1.0.0",
  org.eclipse.e4.ui.dialogs;bundle-version="1.3.0",
  org.eclipse.jface;bundle-version="3.24.0"
 Import-Package: javax.annotation;version="1.3.5",
diff --git a/ui/org.eclipse.pde.spy.preferences/pom.xml b/ui/org.eclipse.pde.spy.preferences/pom.xml
index 6cfd414..59ea58c 100644
--- a/ui/org.eclipse.pde.spy.preferences/pom.xml
+++ b/ui/org.eclipse.pde.spy.preferences/pom.xml
@@ -13,7 +13,7 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
@@ -22,9 +22,7 @@
     <skipAPIAnalysis>true</skipAPIAnalysis>
   </properties>
 
-
-  <groupId>org.eclipse.e4</groupId>
   <artifactId>org.eclipse.pde.spy.preferences</artifactId>
-  <version>0.12.100-SNAPSHOT</version>
+  <version>0.12.200-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/ui/org.eclipse.pde.ui.templates.tests/pom.xml b/ui/org.eclipse.pde.ui.templates.tests/pom.xml
index 75316f9..7209c50 100644
--- a/ui/org.eclipse.pde.ui.templates.tests/pom.xml
+++ b/ui/org.eclipse.pde.ui.templates.tests/pom.xml
@@ -13,11 +13,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>tests-pom</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../tests-pom/</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ui.templates.tests</artifactId>
   <version>1.1.100-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
diff --git a/ui/org.eclipse.pde.ui.templates.tests/src/org/eclipse/pde/ui/templates/tests/TestPDETemplates.java b/ui/org.eclipse.pde.ui.templates.tests/src/org/eclipse/pde/ui/templates/tests/TestPDETemplates.java
index f590aa6..34e92a2 100644
--- a/ui/org.eclipse.pde.ui.templates.tests/src/org/eclipse/pde/ui/templates/tests/TestPDETemplates.java
+++ b/ui/org.eclipse.pde.ui.templates.tests/src/org/eclipse/pde/ui/templates/tests/TestPDETemplates.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2017, 2021 Red Hat Inc. and others.
+ * Copyright (c) 2017, 2022 Red Hat Inc. and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -185,8 +185,7 @@
 				launchPlugins.add(pluginModel);
 		}
 
-		ProductValidationOperation validationOperation = new ProductValidationOperation(
-				launchPlugins.toArray(new IPluginModelBase[0]));
+		ProductValidationOperation validationOperation = new ProductValidationOperation(launchPlugins);
 		validationOperation.run(new NullProgressMonitor());
 
 		if (validationOperation.hasErrors()) {
diff --git a/ui/org.eclipse.pde.ui.templates/pom.xml b/ui/org.eclipse.pde.ui.templates/pom.xml
index 5acb11a..2c5d8c4 100644
--- a/ui/org.eclipse.pde.ui.templates/pom.xml
+++ b/ui/org.eclipse.pde.ui.templates/pom.xml
@@ -13,11 +13,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ui.templates</artifactId>
   <version>3.7.500-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/e4/E4HandlerTemplate.java b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/e4/E4HandlerTemplate.java
index e165dab..160851e 100644
--- a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/e4/E4HandlerTemplate.java
+++ b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/e4/E4HandlerTemplate.java
@@ -112,7 +112,7 @@
 		extension.setId(getValue(KEY_PACKAGE_NAME) + ".fragment"); //$NON-NLS-1$
 
 		element.setName("fragment"); //$NON-NLS-1$
-		element.setAttribute("apply", "initial"); //$NON-NLS-1$ //$NON-NLS-2$
+		element.setAttribute("apply", "always"); //$NON-NLS-1$ //$NON-NLS-2$
 		element.setAttribute("uri", E4_FRAGMENT_FILE); //$NON-NLS-1$
 
 		extension.add(element);
diff --git a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/e4/E4ToolbarContributionTemplate.java b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/e4/E4ToolbarContributionTemplate.java
index 3bb0cbb..64a4f83 100644
--- a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/e4/E4ToolbarContributionTemplate.java
+++ b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/e4/E4ToolbarContributionTemplate.java
@@ -113,7 +113,7 @@
 		extension.setId(getValue(KEY_PACKAGE_NAME) + ".fragment"); //$NON-NLS-1$
 
 		element.setName("fragment"); //$NON-NLS-1$
-		element.setAttribute("apply", "initial"); //$NON-NLS-1$ //$NON-NLS-2$
+		element.setAttribute("apply", "apply"); //$NON-NLS-1$ //$NON-NLS-2$
 		element.setAttribute("uri", E4_FRAGMENT_FILE); //$NON-NLS-1$
 
 		extension.add(element);
diff --git a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/pderesources.properties b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/pderesources.properties
index cba726e..2ae4dce 100644
--- a/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/pderesources.properties
+++ b/ui/org.eclipse.pde.ui.templates/src/org/eclipse/pde/internal/ui/templates/pderesources.properties
@@ -170,7 +170,7 @@
 ViewTemplate_tree = T&ree viewer
 ViewTemplate_contextHelp=Add conte&xt help to the view
 ViewTemplate_addToPerspective = A&dd the view to the java perspective
-ViewTemplate_addViewID = Add a static attribute containing the view &ID
+ViewTemplate_addViewID = Add a static attribute containing the vi&ew ID
 
 HelpTemplate_title = Sample Help Table of Contents
 HelpTemplate_desc = Create a standalone or integrated table of contents.
diff --git a/ui/org.eclipse.pde.ui.templates/templates_3.1/importWizard/java/$wizardPageClassName$.java b/ui/org.eclipse.pde.ui.templates/templates_3.1/importWizard/java/$wizardPageClassName$.java
index 1ea078f..b01c59e 100644
--- a/ui/org.eclipse.pde.ui.templates/templates_3.1/importWizard/java/$wizardPageClassName$.java
+++ b/ui/org.eclipse.pde.ui.templates/templates_3.1/importWizard/java/$wizardPageClassName$.java
@@ -96,6 +96,6 @@
 	 * @see org.eclipse.ui.dialogs.WizardNewFileCreationPage#validateLinkedResource()
 	 */
 	protected IStatus validateLinkedResource() {
-		return new Status(IStatus.OK, "$pluginId$", IStatus.OK, "", null); //$NON-NLS-1$ //$NON-NLS-2$
+		return Status.OK_STATUS;
 	}
 }
diff --git a/ui/org.eclipse.pde.ui.templates/templates_3.1/newWizard/java/$wizardClassName$.java b/ui/org.eclipse.pde.ui.templates/templates_3.1/newWizard/java/$wizardClassName$.java
index e646ecc..d242942 100644
--- a/ui/org.eclipse.pde.ui.templates/templates_3.1/newWizard/java/$wizardClassName$.java
+++ b/ui/org.eclipse.pde.ui.templates/templates_3.1/newWizard/java/$wizardClassName$.java
@@ -93,7 +93,7 @@
 		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
 		IResource resource = root.findMember(new Path(containerName));
 		if (!resource.exists() || !(resource instanceof IContainer)) {
-			throwCoreException("Container \"" + containerName + "\" does not exist.");
+			throw new CoreException(Status.error("Container \"" + containerName + "\" does not exist."));
 		}
 		IContainer container = (IContainer) resource;
 		final IFile file = container.getFile(new Path(fileName));
@@ -130,12 +130,6 @@
 		return new ByteArrayInputStream(contents.getBytes());
 	}
 
-	private void throwCoreException(String message) throws CoreException {
-		IStatus status =
-			new Status(IStatus.ERROR, "$pluginId$", IStatus.OK, message, null);
-		throw new CoreException(status);
-	}
-
 	/**
 	 * We will accept the selection in the workbench to see if
 	 * we can initialize from it.
diff --git a/ui/org.eclipse.pde.ui.templates/templates_3.5/E4ToolbarContribution/fragment.e4xmi b/ui/org.eclipse.pde.ui.templates/templates_3.5/E4ToolbarContribution/fragment.e4xmi
index fa3ae31..6074e29 100644
--- a/ui/org.eclipse.pde.ui.templates/templates_3.5/E4ToolbarContribution/fragment.e4xmi
+++ b/ui/org.eclipse.pde.ui.templates/templates_3.5/E4ToolbarContribution/fragment.e4xmi
@@ -1,13 +1,18 @@
 <?xml version="1.0" encoding="ASCII"?>
 <fragment:ModelFragments xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:commands="http://www.eclipse.org/ui/2010/UIModel/application/commands" xmlns:fragment="http://www.eclipse.org/ui/2010/UIModel/fragment" xmlns:menu="http://www.eclipse.org/ui/2010/UIModel/application/ui/menu" xmi:id="_BxaXACerEeWxCPrV0pAZQQ">
   <fragments xsi:type="fragment:StringModelFragment" xmi:id="_QqSikIrOEeW7h_qdP9N9fw" featurename="commands" parentElementId="xpath:/">
-    <elements xsi:type="commands:Command" xmi:id="_UCYfwIrOEeW7h_qdP9N9fw" elementId="test.handler.helloWorldCommand" commandName="Hello World"/>
+    <elements xsi:type="commands:Command" xmi:id="_UCYfwIrOEeW7h_qdP9N9fw" elementId="test.handler.helloWorldCommand" commandName="Hello World">
+      <persistedState key="persistState" value="false"/>
+    </elements>
   </fragments>
   <fragments xsi:type="fragment:StringModelFragment" xmi:id="_fW12kIrOEeW7h_qdP9N9fw" featurename="handlers" parentElementId="xpath:/">
-    <elements xsi:type="commands:Handler" xmi:id="_k2L0IIrOEeW7h_qdP9N9fw" elementId="org.eclipse.pde.ui.templates.handler.0" contributionURI="bundleclass://$pluginId$/$packageName$.handlers.$className$" command="_UCYfwIrOEeW7h_qdP9N9fw"/>
+    <elements xsi:type="commands:Handler" xmi:id="_k2L0IIrOEeW7h_qdP9N9fw" elementId="org.eclipse.pde.ui.templates.handler.0" contributionURI="bundleclass://$pluginId$/$packageName$.handlers.$className$" command="_UCYfwIrOEeW7h_qdP9N9fw">
+      <persistedState key="persistState" value="false"/>
+    </elements>
   </fragments>
   <fragments xsi:type="fragment:StringModelFragment" xmi:id="_wcLoQMX-Eea_bKDmo1phvA" featurename="trimContributions" parentElementId="xpath:/">
     <elements xsi:type="menu:TrimContribution" xmi:id="_z1UuUMX-Eea_bKDmo1phvA" elementId="org.eclipse.pde.ui.templates.trimcontribution.0" parentId="org.eclipse.ui.main.toolbar" positionInParent="after=org.eclipse.ui.workbench.help">
+      <persistedState key="persistState" value="false"/>
       <children xsi:type="menu:ToolBar" xmi:id="_4jlNMMX-Eea_bKDmo1phvA" elementId="org.eclipse.pde.ui.templates.toolbar.0">
         <children xsi:type="menu:HandledToolItem" xmi:id="_5PyL4MX-Eea_bKDmo1phvA" elementId="org.eclipse.pde.ui.templates.handledtoolitem.helloworld" label="Hello world" iconURI="platform:/plugin/$pluginId$/icons/Sample.png" command="_UCYfwIrOEeW7h_qdP9N9fw"/>
       </children>
diff --git a/ui/org.eclipse.pde.ui.tests.smartimport/pom.xml b/ui/org.eclipse.pde.ui.tests.smartimport/pom.xml
index b3dc083..831c098 100644
--- a/ui/org.eclipse.pde.ui.tests.smartimport/pom.xml
+++ b/ui/org.eclipse.pde.ui.tests.smartimport/pom.xml
@@ -10,16 +10,14 @@
 	<modelVersion>4.0.0</modelVersion>
 	<parent>
 		<artifactId>tests-pom</artifactId>
-		<groupId>eclipse.pde.ui</groupId>
+		<groupId>org.eclipse.pde</groupId>
 		<version>4.23.0-SNAPSHOT</version>
 		<relativePath>../../tests-pom/</relativePath>
 	</parent>
-	<groupId>org.eclipse.pde</groupId>
 	<artifactId>org.eclipse.pde.ui.tests.smartimport</artifactId>
 	<version>1.1.100-SNAPSHOT</version>
 	<packaging>eclipse-test-plugin</packaging>
 
-
 	<properties>
 		<systemProperties>-Dignored.errors.regexp=${ignored.errors.regexp}</systemProperties>
 		<tycho.test.jvmArgs>-Xmx512m</tycho.test.jvmArgs>
diff --git a/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF
index 1d451b3..a804e85 100644
--- a/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF
@@ -42,6 +42,7 @@
  org.eclipse.pde.genericeditor.extension,
  org.eclipse.equinox.simpleconfigurator.manipulator;bundle-version="2.1.300"
 Import-Package: org.assertj.core.api;version="3.14.0",
+ org.assertj.core.presentation;version="3.21.0",
  org.junit.jupiter.api.function;version="5.8.1"
 Eclipse-LazyStart: true
 Bundle-RequiredExecutionEnvironment: JavaSE-11
diff --git a/ui/org.eclipse.pde.ui.tests/pom.xml b/ui/org.eclipse.pde.ui.tests/pom.xml
index 7cece1d..5b48d9a 100644
--- a/ui/org.eclipse.pde.ui.tests/pom.xml
+++ b/ui/org.eclipse.pde.ui.tests/pom.xml
@@ -13,11 +13,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>tests-pom</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../tests-pom/</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ui.tests</artifactId>
   <version>3.11.700-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/DependencyManagerTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/DependencyManagerTest.java
index a954c90..a0f64dc 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/DependencyManagerTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/DependencyManagerTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2021, 2021 Hannes Wellmann and others.
+ *  Copyright (c) 2021, 2022 Hannes Wellmann and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -11,158 +11,319 @@
  *  Contributors:
  *     Hannes Wellmann - initial API and implementation
  *     Hannes Wellmann - Bug 577116: Improve test utility method reusability
+ *     Hannes Wellmann - Bug 578336 - DependencyManager: improve method-signatures and tests
  *******************************************************************************/
 package org.eclipse.pde.core.tests.internal;
 
+import static java.util.Map.entry;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.eclipse.pde.internal.core.DependencyManager.findRequirementsClosure;
+import static org.eclipse.pde.internal.core.DependencyManager.Options.INCLUDE_ALL_FRAGMENTS;
+import static org.eclipse.pde.internal.core.DependencyManager.Options.INCLUDE_NON_TEST_FRAGMENTS;
+import static org.eclipse.pde.internal.core.DependencyManager.Options.INCLUDE_OPTIONAL_DEPENDENCIES;
+import static org.osgi.framework.Constants.EXPORT_PACKAGE;
+import static org.osgi.framework.Constants.FRAGMENT_HOST;
+import static org.osgi.framework.Constants.IMPORT_PACKAGE;
+import static org.osgi.framework.Constants.PROVIDE_CAPABILITY;
+import static org.osgi.framework.Constants.REQUIRE_BUNDLE;
+import static org.osgi.framework.Constants.REQUIRE_CAPABILITY;
 
 import java.io.IOException;
-import java.net.URI;
+import java.nio.file.Path;
 import java.util.*;
 import java.util.Map.Entry;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.osgi.service.resolver.BundleDescription;
-import org.eclipse.osgi.service.resolver.State;
+import org.eclipse.osgi.service.resolver.VersionRange;
+import org.eclipse.pde.core.plugin.IPluginModelBase;
 import org.eclipse.pde.core.plugin.PluginRegistry;
-import org.eclipse.pde.core.target.ITargetLocation;
-import org.eclipse.pde.core.target.ITargetPlatformService;
-import org.eclipse.pde.internal.core.*;
-import org.eclipse.pde.internal.core.target.IUBundleContainer;
-import org.eclipse.pde.ui.tests.target.IUBundleContainerTests;
+import org.eclipse.pde.core.target.NameVersionDescriptor;
+import org.eclipse.pde.internal.core.ClasspathComputer;
+import org.eclipse.pde.internal.core.PluginModelManager;
+import org.eclipse.pde.ui.tests.launcher.AbstractLaunchTest;
 import org.eclipse.pde.ui.tests.util.ProjectUtils;
 import org.eclipse.pde.ui.tests.util.TargetPlatformUtil;
 import org.junit.*;
+import org.junit.rules.TemporaryFolder;
 import org.junit.rules.TestRule;
-import org.osgi.framework.Version;
+import org.osgi.framework.Constants;
 
 public class DependencyManagerTest {
 
 	@ClassRule
+	public static final TestRule RESTORE_TARGET_DEFINITION = TargetPlatformUtil.RESTORE_CURRENT_TARGET_DEFINITION_AFTER;
+	@ClassRule
 	public static final TestRule CLEAR_WORKSPACE = ProjectUtils.DELETE_ALL_WORKSPACE_PROJECTS_BEFORE_AND_AFTER;
+
 	@Rule
 	public final TestRule deleteCreatedTestProjectsAfter = ProjectUtils.DELETE_CREATED_WORKSPACE_PROJECTS_AFTER;
-
 	@Rule
-	public final TestRule restoreTargetDefinition = TargetPlatformUtil.RESTORE_CURRENT_TARGET_DEFINITION_AFTER;
+	public TemporaryFolder folder = new TemporaryFolder();
+	private Path tpJarDirectory;
 
 	@Before
-	public void ensurePluginModelManagerIsInitialized() {
+	public void setupBefore() throws IOException {
+		tpJarDirectory = folder.newFolder("TPJarDirectory").toPath();
+		// ensure PluginModelManager is initialized
 		PluginModelManager.getInstance().getState();
 	}
 
 	@Test
-	public void testFindRequirementsClosure_RequireBundle() throws IOException, CoreException {
-		BundleDescription bundleA = importProjectBundle("tests/projects/requirements/bundle.A-1.0.0");
-		BundleDescription bundle = importProjectBundle("tests/projects/requirements/bundle-RequireBundle");
+	public void testFindRequirementsClosure_requireBundle() throws Exception {
 
-		State state = TargetPlatformHelper.getState();
-		BundleDescription osgiBundle = state.getBundle("org.eclipse.osgi", null);
-		BundleDescription osgiBundleFragment = osgiBundle.getFragments()[0];
+		setTargetPlatform( //
+				bundle("bundle.a", "1.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.a.pack" + version("1.0.0"))),
+
+				bundle("bundle.requireBundle", "1.0.0", //
+						entry(REQUIRE_BUNDLE, "bundle.a" + bundleVersion("1.0.0", "1.1.0"))));
+
+		BundleDescription bundleA = bundleDescription("bundle.a", "1.0.0");
+		BundleDescription bundle = bundleDescription("bundle.requireBundle", "1.0.0");
 
 		Set<BundleDescription> bundles = Set.of(bundle);
-		Set<BundleDescription> closure = DependencyManager.findRequirementsClosure(bundles, false);
-		assertThat(closure).isEqualTo(Set.of(bundleA, bundle, osgiBundle, osgiBundleFragment));
+		Set<BundleDescription> closure = findRequirementsClosure(bundles);
+		assertThat(closure).isEqualTo(Set.of(bundleA, bundle));
 	}
 
 	@Test
-	public void testFindRequirementsClosure_RequireBundle2() throws Exception {
-		URI locationURI = IUBundleContainerTests.getURI("tests/sites/site.a.b");
-		Map<URI, List<Entry<String, Version>>> locationIUs = Map.of(locationURI,
-				List.of(Map.entry("feature.a.feature.group", Version.emptyVersion)));
-		loadIUTarget(locationIUs);
+	public void testFindRequirementsClosure_requireBundle2() throws Exception {
 
-		State state = TargetPlatformHelper.getState();
-		BundleDescription bundle3 = state.getBundle("bundle.a3", null);
-		BundleDescription bundle2 = state.getBundle("bundle.a2", null);
-		BundleDescription bundle1 = state.getBundle("bundle.a1", null);
+		setTargetPlatform( //
+				bundle("bundle.a1", "1.0.0"),
+
+				bundle("bundle.a2", "1.0.0", //
+						entry(REQUIRE_BUNDLE, "bundle.a1")),
+
+				bundle("bundle.a3", "1.0.0", //
+						entry(REQUIRE_BUNDLE, "bundle.a2")));
+
+		BundleDescription bundle3 = bundleDescription("bundle.a3", "1.0.0");
+		BundleDescription bundle2 = bundleDescription("bundle.a2", "1.0.0");
+		BundleDescription bundle1 = bundleDescription("bundle.a1", "1.0.0");
 
 		Set<BundleDescription> bundles = Set.of(bundle3);
-		Set<BundleDescription> closure = DependencyManager.findRequirementsClosure(bundles, false);
+		Set<BundleDescription> closure = findRequirementsClosure(bundles);
 		assertThat(closure).isEqualTo(Set.of(bundle3, bundle2, bundle1));
 	}
 
 	@Test
-	public void testFindRequirementsClosure_ImportPackage() throws IOException, CoreException {
-		BundleDescription bundleA = importProjectBundle("tests/projects/requirements/bundle.A-2.0.0");
-		BundleDescription bundle = importProjectBundle("tests/projects/requirements/bundle-ImportPackage");
+	public void testFindRequirementsClosure_importPackage() throws Exception {
 
-		State state = TargetPlatformHelper.getState();
-		BundleDescription osgiBundle = state.getBundle("org.eclipse.osgi", null);
-		BundleDescription osgiBundleFragment = osgiBundle.getFragments()[0];
+		setTargetPlatform( //
+				bundle("bundle.a", "2.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.a.pack" + version("2.0.0"))),
+
+				bundle("bundle.importPackage", "1.0.0", //
+						entry(IMPORT_PACKAGE, "bundle.a.pack")));
+
+		BundleDescription bundleA = bundleDescription("bundle.a", "2.0.0");
+		BundleDescription bundle = bundleDescription("bundle.importPackage", "1.0.0");
 
 		Set<BundleDescription> bundles = Set.of(bundle);
-		Set<BundleDescription> closure = DependencyManager.findRequirementsClosure(bundles, false);
-		assertThat(closure).isEqualTo(Set.of(bundleA, bundle, osgiBundle, osgiBundleFragment));
+		Set<BundleDescription> closure = findRequirementsClosure(bundles);
+		assertThat(closure).isEqualTo(Set.of(bundleA, bundle));
 	}
 
 	@Test
-	public void testFindRequirementsClosure_RequiredCapability() throws IOException, CoreException {
-		BundleDescription consumerDescription = importProjectBundle(
-				"tests/projects/capabilities/capabilities.consumer");
-		BundleDescription providerDescription = importProjectBundle(
-				"tests/projects/capabilities/capabilities.provider");
+	public void testFindRequirementsClosure_requiredCapability() throws Exception {
 
-		State state = TargetPlatformHelper.getState();
-		BundleDescription osgiBundle = state.getBundle("org.eclipse.osgi", null);
-		BundleDescription osgiBundleFragment = osgiBundle.getFragments()[0];
+		setTargetPlatform( //
+				bundle("capabilities.provider", "1.0.0", //
+						entry(PROVIDE_CAPABILITY, "some.test.capability")),
+
+				bundle("capabilities.consumer", "1.0.0", //
+						entry(REQUIRE_CAPABILITY, "some.test.capability")));
+
+		BundleDescription consumerDescription = bundleDescription("capabilities.consumer", "1.0.0");
+		BundleDescription providerDescription = bundleDescription("capabilities.provider", "1.0.0");
 
 		Set<BundleDescription> bundles = Set.of(consumerDescription);
-		Set<BundleDescription> closure = DependencyManager.findRequirementsClosure(bundles, false);
-		assertThat(closure).isEqualTo(Set.of(consumerDescription, providerDescription, osgiBundle, osgiBundleFragment));
+		Set<BundleDescription> closure = findRequirementsClosure(bundles);
+		assertThat(closure).isEqualTo(Set.of(consumerDescription, providerDescription));
 	}
 
 	@Test
-	public void testFindRequirementsClosure_RequireDifferentVersions() throws IOException, CoreException {
-		BundleDescription bundleA1 = importProjectBundle("tests/projects/requirements/bundle.A-1.0.0");
-		BundleDescription bundleA2 = importProjectBundle("tests/projects/requirements/bundle.A-2.0.0");
-		BundleDescription bundleImportPackage = importProjectBundle("tests/projects/requirements/bundle-ImportPackage");
-		BundleDescription bundleRequireBundle = importProjectBundle("tests/projects/requirements/bundle-RequireBundle");
+	public void testFindRequirementsClosure_requireDifferentVersions() throws Exception {
+
+		setTargetPlatform( //
+				bundle("bundle.a", "1.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.a.pack" + version("1.0.0"))),
+
+				bundle("bundle.a", "2.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.a.pack" + version("2.0.0"))),
+
+				bundle("bundle.importPackage", "1.0.0", //
+						entry(IMPORT_PACKAGE, "bundle.a.pack" + version("2.0.0"))),
+
+				bundle("bundle.requireBundle", "1.0.0", //
+						entry(REQUIRE_BUNDLE, "bundle.a" + bundleVersion("1.0.0", "1.1.0"))));
+
+		BundleDescription bundleA1 = bundleDescription("bundle.a", "1.0.0");
+		BundleDescription bundleA2 = bundleDescription("bundle.a", "2.0.0");
+		BundleDescription bundleImportPackage = bundleDescription("bundle.importPackage", "1.0.0");
+		BundleDescription bundleRequireBundle = bundleDescription("bundle.requireBundle", "1.0.0");
 		// bundleImportPackage requires bundle.a 2.0.0
 		// bundleRequireBundle requires bundle.a 1.0.0
 
-		State state = TargetPlatformHelper.getState();
-		BundleDescription osgiBundle = state.getBundle("org.eclipse.osgi", null);
-		BundleDescription osgiBundleFragment = osgiBundle.getFragments()[0];
-
 		Set<BundleDescription> bundles = Set.of(bundleImportPackage, bundleRequireBundle);
-		Set<BundleDescription> closure = DependencyManager.findRequirementsClosure(bundles, false);
-		assertThat(closure).isEqualTo(
-				Set.of(bundleImportPackage, bundleRequireBundle, bundleA1, bundleA2, osgiBundle, osgiBundleFragment));
+		Set<BundleDescription> closure = findRequirementsClosure(bundles);
+		assertThat(closure).isEqualTo(Set.of(bundleImportPackage, bundleRequireBundle, bundleA1, bundleA2));
 	}
 
 	@Test
-	public void testFindRequirementsClosure_IncludeFragments() throws IOException, CoreException {
-		BundleDescription bundleA = importProjectBundle("tests/projects/requirements/bundle.A-1.0.0");
-		BundleDescription bundleFragment = importProjectBundle("tests/projects/requirements/bundle-Fragment");
+	public void testFindRequirementsClosure_includeFragments() throws Exception {
 
-		State state = TargetPlatformHelper.getState();
-		BundleDescription osgiBundle = state.getBundle("org.eclipse.osgi", null);
-		BundleDescription osgiBundleFragment = osgiBundle.getFragments()[0];
+		setTargetPlatform( //
+				bundle("bundle.a", "1.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.a.pack" + version("1.0.0"))),
+
+				bundle("bundle.fragment", "1.0.0", //
+						entry(FRAGMENT_HOST, "bundle.a")),
+
+				bundle("bundle.fragment.with.dependencies", "1.0.0", //
+						entry(FRAGMENT_HOST, "bundle.a"), //
+						entry(REQUIRE_BUNDLE, "bundle.b"), //
+						entry(IMPORT_PACKAGE, "bundle.c.pack")),
+
+				bundle("bundle.b", "1.0.0"), //
+
+				bundle("bundle.c", "1.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.c.pack")),
+
+				bundle("bundle.d", "1.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.d.pack")));
+
+		BundleDescription bundleA = bundleDescription("bundle.a", "1.0.0");
+		BundleDescription bundleB = bundleDescription("bundle.b", "1.0.0");
+		BundleDescription bundleC = bundleDescription("bundle.c", "1.0.0");
+		BundleDescription bundleFragment = bundleDescription("bundle.fragment", "1.0.0");
+		BundleDescription bundleFragmentWithDeps = bundleDescription("bundle.fragment.with.dependencies", "1.0.0");
 
 		Set<BundleDescription> bundles = Set.of(bundleA);
-		Set<BundleDescription> closure = DependencyManager.findRequirementsClosure(bundles, false);
-		assertThat(closure).isEqualTo(Set.of(bundleA, bundleFragment, osgiBundle, osgiBundleFragment));
+
+		Set<BundleDescription> noFragmentsClosure = findRequirementsClosure(bundles);
+		assertThat(noFragmentsClosure).isEqualTo(Set.of(bundleA));
+
+		Set<BundleDescription> allFragmentsClosure = findRequirementsClosure(bundles, INCLUDE_ALL_FRAGMENTS);
+		assertThat(allFragmentsClosure)
+				.isEqualTo(Set.of(bundleA, bundleFragment, bundleFragmentWithDeps, bundleB, bundleC));
+	}
+
+	@Test
+	public void testFindRequirementsClosure_includeNonTestFragments() throws Exception {
+
+		setTargetPlatform( //
+				bundle("bundle.a", "1.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.a.pack" + version("1.0.0"))),
+
+				bundle("bundle.fragment", "1.0.0", //
+						entry(FRAGMENT_HOST, "bundle.a")),
+
+				bundle("other.tests", "1.0.0", //
+						entry(FRAGMENT_HOST, "bundle.a")));
+
+		BundleDescription bundleA = bundleDescription("bundle.a", "1.0.0");
+		BundleDescription bundleFragment = bundleDescription("bundle.fragment", "1.0.0");
+		BundleDescription otherFragmentWithTestName = bundleDescription("other.tests", "1.0.0");
+
+		BundleDescription testFragmentWithTestAttr = createFragmentProject("bundle.a1.tests", "bundle.a", true);
+		BundleDescription testFragmentWithOtherName = createFragmentProject("bundle.a.checks", "bundle.a", true);
+		BundleDescription testFragmentWithoutTestAttr = createFragmentProject("bundle.a2.tests", "bundle.a", false);
+
+		Set<BundleDescription> bundles = Set.of(bundleA);
+
+		Set<BundleDescription> noFragmentsClosure = findRequirementsClosure(bundles);
+		assertThat(noFragmentsClosure).isEqualTo(Set.of(bundleA));
+
+		Set<BundleDescription> allFragmentsClosure = findRequirementsClosure(bundles, INCLUDE_ALL_FRAGMENTS);
+		assertThat(allFragmentsClosure).isEqualTo(Set.of(bundleA, bundleFragment, otherFragmentWithTestName,
+				testFragmentWithTestAttr, testFragmentWithOtherName, testFragmentWithoutTestAttr));
+
+		Set<BundleDescription> nonTestFragmentsClosure = findRequirementsClosure(bundles, INCLUDE_NON_TEST_FRAGMENTS);
+		assertThat(nonTestFragmentsClosure)
+		.isEqualTo(Set.of(bundleA, bundleFragment, otherFragmentWithTestName, testFragmentWithoutTestAttr));
+	}
+
+	@Test
+	public void testFindRequirementsClosure_includeOptional() throws Exception {
+
+		setTargetPlatform( //
+				bundle("bundle.a", "1.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.a.pack" + version("1.0.0"))),
+
+				bundle("bundle.a", "2.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.a.pack" + version("2.0.0"))),
+
+				bundle("capabilities.provider", "1.0.0", //
+						entry(PROVIDE_CAPABILITY, "some.test.capability")),
+
+				bundle("bundle.optional", "1.0.0", //
+						entry(IMPORT_PACKAGE, "bundle.a.pack" + version("2.0.0") + resolution(OPTIONAL)), //
+						entry(REQUIRE_BUNDLE, "bundle.a" + bundleVersion("1.0.0", "1.1.0") + resolution(OPTIONAL)), //
+						entry(REQUIRE_CAPABILITY, "some.test.capability" + resolution(OPTIONAL))));
+
+		BundleDescription bundleA1 = bundleDescription("bundle.a", "1.0.0");
+		BundleDescription bundleA2 = bundleDescription("bundle.a", "2.0.0");
+		BundleDescription bundleProvider = bundleDescription("capabilities.provider", "1.0.0");
+		BundleDescription bundleOptional = bundleDescription("bundle.optional", "1.0.0");
+
+		Set<BundleDescription> bundles = Set.of(bundleOptional);
+
+		Set<BundleDescription> noOptionalClosure = findRequirementsClosure(bundles);
+		assertThat(noOptionalClosure).isEqualTo(Set.of(bundleOptional));
+
+		Set<BundleDescription> optionalClosure = findRequirementsClosure(bundles, INCLUDE_OPTIONAL_DEPENDENCIES);
+		assertThat(optionalClosure).isEqualTo(Set.of(bundleOptional, bundleA1, bundleA2, bundleProvider));
 	}
 
 	// --- utility methods ---
 
-	private BundleDescription importProjectBundle(String projectPath) throws IOException, CoreException {
-		IProject project = ProjectUtils.importTestProject(projectPath);
-		return PluginRegistry.findModel(project).getBundleDescription();
+	@SafeVarargs
+	private void setTargetPlatform(Map.Entry<NameVersionDescriptor, Map<String, String>>... pluginDescriptions)
+			throws IOException, InterruptedException {
+		TargetPlatformUtil.setDummyBundlesAsTarget(Map.ofEntries(pluginDescriptions), tpJarDirectory);
 	}
 
-	private void loadIUTarget(Map<URI, List<Entry<String, Version>>> locationIUs) throws Exception {
-		ITargetPlatformService tps = PDECore.getDefault().acquireService(ITargetPlatformService.class);
-		List<ITargetLocation> locations = new ArrayList<>();
-		locationIUs.forEach((locationURI, ius) -> {
-			String[] unitIds = ius.stream().map(Entry::getKey).toArray(String[]::new);
-			String[] versions = ius.stream().map(Entry::getValue).map(Version::toString).toArray(String[]::new);
-			URI[] repositories = new URI[] { locationURI };
-			ITargetLocation location = tps.newIULocation(unitIds, versions, repositories,
-					IUBundleContainer.INCLUDE_REQUIRED | IUBundleContainer.INCLUDE_CONFIGURE_PHASE);
-			locations.add(location);
+	@SafeVarargs
+	private static Entry<NameVersionDescriptor, Map<String, String>> bundle(String id, String version,
+			Entry<String, String>... additionalManifestEntries) {
+		return entry(new NameVersionDescriptor(id, version), Map.ofEntries(additionalManifestEntries));
+	}
+
+	private static String version(String version) {
+		return ";" + Constants.VERSION_ATTRIBUTE + "=\"" + version + "\"";
+	}
+
+	private static String bundleVersion(String lowerBound, String upperBound) {
+		return ";" + Constants.BUNDLE_VERSION_ATTRIBUTE + "=\"[" + lowerBound + "," + upperBound + ")\"";
+	}
+
+	private static String resolution(String resolutionType) {
+		return ";" + Constants.RESOLUTION_DIRECTIVE + ":=\"" + resolutionType + "\"";
+	}
+
+	private static final String OPTIONAL = Constants.RESOLUTION_OPTIONAL;
+
+	private static BundleDescription bundleDescription(String id, String version) {
+		return AbstractLaunchTest.findTargetModel(id, version).getBundleDescription();
+	}
+
+	private static BundleDescription createFragmentProject(String projectName, String hostName,
+			boolean setTestAttribute) throws CoreException {
+
+		IProject project = ProjectUtils.createPluginProject(projectName, projectName, "1.0.0", (d, s) -> {
+			d.setHost(s.newHost(hostName, VersionRange.emptyRange));
 		});
-		TargetPlatformUtil.createAndSetTargetForWorkspace(null, locations, null);
+		IPluginModelBase model = PluginRegistry.findModel(project);
+		if (setTestAttribute) { // set test attribute in classpath
+			IClasspathEntry[] classpath = ClasspathComputer.getClasspath(project, model, null, false, true);
+			var cpEntries = Arrays.stream(classpath).map(e -> ClasspathComputer.updateTestAttribute(true, e));
+			JavaCore.create(project).setRawClasspath(cpEntries.toArray(IClasspathEntry[]::new), null);
+		}
+		return model.getBundleDescription();
 	}
 }
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/PDETestCase.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/PDETestCase.java
index 39d7cc0..acf7a7d 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/PDETestCase.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/PDETestCase.java
@@ -17,6 +17,7 @@
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.pde.ui.tests.runtime.TestUtils;
+import org.eclipse.pde.ui.tests.util.FreezeMonitor;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.*;
 import org.eclipse.ui.intro.IIntroManager;
@@ -38,12 +39,13 @@
 
 	@Before
 	public void setUp() throws Exception {
+		FreezeMonitor.expectCompletionInAMinute();
 		TestUtils.log(IStatus.INFO, name.getMethodName(), "setUp");
 		assertWelcomeScreenClosed();
 	}
 
 	@After
-	public void tearDown() {
+	public void tearDown() throws Exception {
 		TestUtils.log(IStatus.INFO, name.getMethodName(), "tearDown");
 		// Close any editors we opened
 		IWorkbenchWindow[] workbenchPages = PlatformUI.getWorkbench().getWorkbenchWindows();
@@ -65,6 +67,7 @@
 		} catch (CoreException e) {
 		}
 		TestUtils.waitForJobs(name.getMethodName(), 10, 10000);
+		FreezeMonitor.done();
 	}
 
 	/**
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/classpathresolver/ClasspathResolverTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/classpathresolver/ClasspathResolverTest.java
index 36613d4..05bfc3b 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/classpathresolver/ClasspathResolverTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/classpathresolver/ClasspathResolverTest.java
@@ -122,6 +122,7 @@
 
 		String expectedDevCP = project.getFolder("cpe").getLocation().toPortableString();
 		assertEquals(expectedDevCP, properties.get(bundleName));
+		assertEquals(expectedDevCP, properties.get(bundleName + ";1.0.0.qualifier"));
 	}
 
 	/**
@@ -155,7 +156,8 @@
 
 		assertEquals("true", devProperties.getProperty("@ignoredot@"));
 		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID));
-		assertEquals(2, devProperties.size()); // assert no more entries
+		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID + ";2.0.0"));
+		assertEquals(3, devProperties.size()); // assert no more entries
 	}
 
 	@Test
@@ -173,7 +175,8 @@
 
 		assertEquals("true", devProperties.getProperty("@ignoredot@"));
 		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID));
-		assertEquals(2, devProperties.size()); // assert no more entries
+		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID + ";" + hostBundleVersion));
+		assertEquals(3, devProperties.size()); // assert no more entries
 	}
 
 	@Test
@@ -189,8 +192,9 @@
 		Properties devProperties = createDevEntryProperties(List.of(hostModel));
 
 		assertEquals("true", devProperties.getProperty("@ignoredot@"));
-		assertEquals("devPath1", devProperties.getProperty(HOST_BUNDLE_ID));
-		assertEquals(2, devProperties.size()); // assert no more entries
+		assertEquals("devPath2", devProperties.getProperty(HOST_BUNDLE_ID));
+		assertEquals("devPath2", devProperties.getProperty(HOST_BUNDLE_ID + ";" + hostBundleVersion));
+		assertEquals(3, devProperties.size()); // assert no more entries
 	}
 
 	@Test
@@ -247,8 +251,10 @@
 		Properties devProperties = createDevEntryProperties(List.of(hostModel, wsModel));
 
 		assertEquals("true", devProperties.getProperty("@ignoredot@"));
-		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID));
-		assertEquals(2, devProperties.size()); // assert no more entries
+		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID)); // last
+		assertEquals("", devProperties.getProperty(HOST_BUNDLE_ID + ";1.0.0"));
+		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID + ";2.0.0"));
+		assertEquals(4, devProperties.size()); // assert no more entries
 	}
 
 	@Test
@@ -266,8 +272,10 @@
 		Properties devProperties = createDevEntryProperties(List.of(tpModel, hostModel));
 
 		assertEquals("true", devProperties.getProperty("@ignoredot@"));
-		assertEquals("devPath1", devProperties.getProperty(HOST_BUNDLE_ID));
-		assertEquals(2, devProperties.size()); // assert no more entries
+		assertEquals("devPath2", devProperties.getProperty(HOST_BUNDLE_ID)); // last
+		assertEquals("", devProperties.getProperty(HOST_BUNDLE_ID + ";1.0.0"));
+		assertEquals("devPath2", devProperties.getProperty(HOST_BUNDLE_ID + ";" + hostBundleVersion));
+		assertEquals(4, devProperties.size()); // assert no more entries
 	}
 
 	@Test
@@ -284,8 +292,10 @@
 		Properties devProperties = createDevEntryProperties(List.of(hostModel, wsModel));
 
 		assertEquals("true", devProperties.getProperty("@ignoredot@"));
-		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID));
-		assertEquals(2, devProperties.size()); // assert no more entries
+		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID)); // last
+		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID + ";2.0.0"));
+		assertEquals("devPath2", devProperties.getProperty(HOST_BUNDLE_ID + ";" + hostBundleVersion));
+		assertEquals(4, devProperties.size()); // assert no more entries
 	}
 
 	@Test
@@ -301,11 +311,15 @@
 		IPluginModelBase tpModel = findTargetModel(HOST_BUNDLE_ID, "1.0.0");
 		IPluginModelBase wsModel = findWorkspaceModel(HOST_BUNDLE_ID, "2.0.0");
 
-		Properties devProperties = createDevEntryProperties(List.of(hostModel, tpModel, wsModel));
+		Properties devProperties = createDevEntryProperties(List.of(hostModel, wsModel, tpModel));
 
 		assertEquals("true", devProperties.getProperty("@ignoredot@"));
+		// jar-bundle from tp should not be considered for non-version entry
 		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID));
-		assertEquals(2, devProperties.size()); // assert no more entries
+		assertEquals("", devProperties.getProperty(HOST_BUNDLE_ID + ";1.0.0"));
+		assertEquals("bin", devProperties.getProperty(HOST_BUNDLE_ID + ";2.0.0"));
+		assertEquals("devPath2", devProperties.getProperty(HOST_BUNDLE_ID + ";" + hostBundleVersion));
+		assertEquals(5, devProperties.size()); // assert no more entries
 	}
 
 	// --- utility methods ---
@@ -372,8 +386,7 @@
 	private Properties createDevEntryProperties(List<IPluginModelBase> launchedBundles)
 			throws IOException, CoreException {
 		File devPropertiesFile = tempFolder.newFile("dev.properties").getCanonicalFile();
-		Map<String, IPluginModelBase> bundlesMap = Map.of(HOST_BUNDLE_ID,
-				launchedBundles.get(launchedBundles.size() - 1));
+		Map<String, List<IPluginModelBase>> bundlesMap = Map.of(HOST_BUNDLE_ID, launchedBundles);
 		String devPropertiesURL = ClasspathHelper.getDevEntriesProperties(devPropertiesFile.getPath(), bundlesMap);
 		return loadProperties(devPropertiesURL);
 	}
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/ee/ExecutionEnvironmentTests.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/ee/ExecutionEnvironmentTests.java
index 96d4aca..444eea9 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/ee/ExecutionEnvironmentTests.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/ee/ExecutionEnvironmentTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2008, 2018 IBM Corporation and others.
+ * Copyright (c) 2008, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -195,7 +195,7 @@
 	@Test
 	public void testNoEnvironment() throws Exception {
 		try {
-			IJavaProject project = ProjectUtils.createPluginProject("no.env", null);
+			IJavaProject project = ProjectUtils.createPluginProject("no.env", (IExecutionEnvironment) null);
 			assertTrue("Project was not created", project.exists());
 
 			Hashtable<String, String> options = JavaCore.getOptions();
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/imports/ImportFeatureProjectsTestCase.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/imports/ImportFeatureProjectsTestCase.java
index 589bb60..7bedaf5 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/imports/ImportFeatureProjectsTestCase.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/imports/ImportFeatureProjectsTestCase.java
@@ -35,7 +35,7 @@
 
 	@Override
 	@After
-	public void tearDown() {
+	public void tearDown() throws Exception {
 		fProjectName = null;
 		super.tearDown();
 	}
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/AbstractLaunchTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/AbstractLaunchTest.java
index 9203ea4..3599d18 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/AbstractLaunchTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/AbstractLaunchTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2019, 2021 Julian Honnen and others.
+ *  Copyright (c) 2019, 2022 Julian Honnen and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -16,16 +16,21 @@
  *******************************************************************************/
 package org.eclipse.pde.ui.tests.launcher;
 
+import static java.util.Comparator.comparing;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import java.util.Arrays;
-import java.util.NoSuchElementException;
+import java.util.*;
+import java.util.Map.Entry;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import org.assertj.core.api.Assertions;
+import org.assertj.core.presentation.StandardRepresentation;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.debug.core.*;
 import org.eclipse.pde.core.plugin.*;
+import org.eclipse.pde.core.target.NameVersionDescriptor;
 import org.eclipse.pde.ui.tests.util.ProjectUtils;
 import org.eclipse.pde.ui.tests.util.TargetPlatformUtil;
 import org.junit.*;
@@ -50,7 +55,7 @@
 	@Rule
 	public final TestRule deleteCreatedTestProjectsAfter = ProjectUtils.DELETE_CREATED_WORKSPACE_PROJECTS_AFTER;
 
-	protected ILaunchConfiguration getLaunchConfiguration(String name) {
+	protected static ILaunchConfiguration getLaunchConfiguration(String name) {
 		ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
 		return launchManager.getLaunchConfiguration(launchConfigsProject.getFile(name));
 	}
@@ -78,6 +83,57 @@
 		Stream<IPluginModelBase> candiates = Arrays.stream(models);
 		return candiates.filter(model -> version.equals(Version.parseVersion(model.getPluginBase().getVersion())))
 				.findFirst() // always take first like BundleLaunchHelper
-				.orElseThrow(() -> new NoSuchElementException("No " + type + " model " + id + "-" + version + "found"));
+				.orElseThrow(
+						() -> new NoSuchElementException("No " + type + " model " + id + "-" + version + " found"));
 	}
+
+	static NameVersionDescriptor bundle(String id, String version) {
+		return new NameVersionDescriptor(id, version);
+	}
+
+	static BundleLocationDescriptor workspaceBundle(String id, String version) {
+		Objects.requireNonNull(version);
+		return () -> findWorkspaceModel(id, version);
+	}
+
+	static BundleLocationDescriptor targetBundle(String id, String version) {
+		Objects.requireNonNull(version);
+		// PluginRegistry.findModel does not consider external models when
+		// workspace models are present and returns the 'last' plug-in if
+		// multiple with the same version exist
+		return () -> findTargetModel(id, version);
+	}
+
+	static interface BundleLocationDescriptor {
+		IPluginModelBase findModel();
+	}
+
+	static void assertPluginMapsEquals(String message, Map<IPluginModelBase, String> expected,
+			Map<IPluginModelBase, String> actual) {
+		// Like Assert.assertEquals() but with more expressive and easier to
+		// compare failure message
+		Assertions.assertThat(actual).withRepresentation(new StandardRepresentation() {
+			@Override
+			public String toStringOf(Object object) {
+				if (object instanceof IPluginModelBase) {
+					IPluginModelBase plugin = (IPluginModelBase) object;
+					String location = plugin.getUnderlyingResource() != null ? "w" : "e";
+					IPluginBase p = plugin.getPluginBase();
+					return p.getId() + "-" + p.getVersion() + "(" + location + ")";
+				}
+				if (object instanceof Map) {
+					@SuppressWarnings("unchecked")
+					var entries = ((Map<IPluginModelBase, String>) object).entrySet().stream();
+					return entries.sorted(PLUGIN_COMPARATOR).map(super::toStringOf)
+							.collect(Collectors.joining(",\n", "{\n", "\n}"));
+				}
+				return super.toStringOf(object);
+			}
+		}).as(message).isEqualTo(expected);
+	}
+
+	private static final Comparator<Entry<IPluginModelBase, String>> PLUGIN_COMPARATOR = comparing(Entry::getKey,
+			comparing((IPluginModelBase p) -> p.getPluginBase(),
+					comparing(IPluginBase::getId).thenComparing(IPluginBase::getVersion))
+			.thenComparing((IPluginModelBase p) -> p.getUnderlyingResource() == null));
 }
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java
index f3df502..1d65cea 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2019, 2021 Julian Honnen and others.
+ *  Copyright (c) 2019, 2022 Julian Honnen and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -11,85 +11,1460 @@
  *  Contributors:
  *     Julian Honnen <julian.honnen@vector.com> - initial API and implementation
  *     Andras Peteri <apeteri@b2international.com> - extracted common superclass
- *     Hannes Wellmann - Bug 577116: Improve test utility method reusability
+ *     Hannes Wellmann - Bug 577116 - Improve test utility method reusability
+ *     Hannes Wellmann - Bug 578005 - Extend tests to fully cover feature-based launches
  *******************************************************************************/
 package org.eclipse.pde.ui.tests.launcher;
 
+import static java.util.Collections.emptySet;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.eclipse.pde.internal.core.ICoreConstants.DEFAULT_VERSION;
+import static org.eclipse.pde.ui.tests.util.ProjectUtils.createPluginProject;
 
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
 import java.util.Map.Entry;
-import java.util.stream.Stream;
+import java.util.stream.Collectors;
 import org.eclipse.core.resources.*;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.debug.core.ILaunchConfiguration;
-import org.eclipse.pde.core.plugin.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.debug.core.*;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.pde.core.plugin.IMatchRules;
+import org.eclipse.pde.core.plugin.IPluginModelBase;
+import org.eclipse.pde.core.target.NameVersionDescriptor;
+import org.eclipse.pde.internal.core.FeatureModelManager;
+import org.eclipse.pde.internal.core.PDECore;
+import org.eclipse.pde.internal.core.feature.FeatureChild;
+import org.eclipse.pde.internal.core.feature.WorkspaceFeatureModel;
+import org.eclipse.pde.internal.core.ifeature.*;
 import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
-import org.eclipse.pde.internal.ui.wizards.feature.CreateFeatureProjectOperation;
+import org.eclipse.pde.internal.ui.wizards.feature.AbstractCreateFeatureOperation;
 import org.eclipse.pde.internal.ui.wizards.feature.FeatureData;
+import org.eclipse.pde.launching.IPDELauncherConstants;
+import org.eclipse.pde.ui.tests.util.TargetPlatformUtil;
 import org.junit.*;
+import org.junit.rules.TemporaryFolder;
 
 public class FeatureBasedLaunchTest extends AbstractLaunchTest {
 
-	private static IProject featureProject;
-
-	private ILaunchConfiguration fFeatureBasedWithStartLevels;
-
-	@BeforeClass
-	public static void createTestFeature() throws Exception {
-
-		FeatureData featureData = new FeatureData();
-		featureData.id = FeatureBasedLaunchTest.class.getName() + "-feature";
-		featureData.version = "1.0.0";
-
-		IPluginBase[] contents = Stream.of("javax.inject", "org.eclipse.core.runtime", "org.eclipse.ui") //
-				.map(id -> PluginRegistry.findModel(id).getPluginBase()) //
-				.toArray(IPluginBase[]::new);
-
-		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
-		featureProject = workspaceRoot.getProject(featureData.id);
-		IPath location = workspaceRoot.getLocation().append(featureProject.getName());
-		CreateFeatureProjectOperation operation = new CreateFeatureProjectOperation(featureProject, location,
-				featureData, contents, null) {
-			@Override
-			protected void openFeatureEditor(IFile manifestFile) {
-				// don't open in headless tests
-			}
-		};
-		operation.run(null);
-	}
+	@Rule
+	public TemporaryFolder folder = new TemporaryFolder();
+	private Path tpJarDirectory;
 
 	@Before
-	public void setupLaunchConfig() throws Exception {
-		fFeatureBasedWithStartLevels = getLaunchConfiguration("feature-based-with-startlevels.launch");
+	public void setup() throws Exception {
+		tpJarDirectory = folder.newFolder("TPJarDirectory").toPath();
 	}
 
+	// --- tests ---
+
 	@Test
-	public void testOldEntryWithoutConfigurationHasDefaults() throws Exception {
-		checkStartLevels("javax.inject", "default:default");
-	}
+	public void testGetMergedBundleMap_autostartLevels() throws Throwable {
+		TargetPlatformUtil.setRunningPlatformAsTarget();
 
-	@Test
-	public void testUseConfiguredStartLevels() throws Exception {
-		checkStartLevels("org.eclipse.core.runtime", "1:true");
-	}
+		createFeatureProject(FeatureBasedLaunchTest.class.getName() + "-feature", "1.0.0", f -> {
+			addIncludedPlugin(f, "javax.inject", DEFAULT_VERSION);
+			addIncludedPlugin(f, "org.eclipse.core.runtime", DEFAULT_VERSION);
+			addIncludedPlugin(f, "org.eclipse.ui", DEFAULT_VERSION);
+		});
+		ILaunchConfiguration lc = getLaunchConfiguration("feature-based-with-startlevels.launch");
 
-	@Test
-	public void testIgnoreConfiguredStartLevelsOfUncheckedPlugin() throws Exception {
-		checkStartLevels("org.eclipse.ui", "default:default");
-	}
-
-	private void checkStartLevels(String pluginId, String expectedStartLevels) throws CoreException {
-		Map<IPluginModelBase, String> bundleMap = BundleLauncherHelper.getMergedBundleMap(fFeatureBasedWithStartLevels,
-				false);
+		Map<IPluginModelBase, String> bundleMap = BundleLauncherHelper.getMergedBundleMap(lc, false);
 
 		Map<String, String> byId = new LinkedHashMap<>();
 		for (Entry<IPluginModelBase, String> entry : bundleMap.entrySet()) {
 			byId.put(entry.getKey().getPluginBase().getId(), entry.getValue());
 		}
 
-		assertThat(byId).containsEntry(pluginId, expectedStartLevels);
+		assertThat(byId)//
+		.as("old entry without configuration has defaults").containsEntry("javax.inject", "default:default")
+		.as("use configured start-levels").containsEntry("org.eclipse.core.runtime", "1:true")
+		.as("ignore configured start-levels of uncheckedplugin")
+		.containsEntry("org.eclipse.ui", "default:default");
+	}
+
+	// --- defined feature selection ---
+
+	@Test
+	public void testGetMergedBundleMap_featureSelectionForLocationWorkspace_latestWorkspaceFeature() throws Throwable {
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"), //
+				bundle("plugin.b", "1.0.0"), //
+				bundle("plugin.c", "1.0.0"), //
+				bundle("plugin.d", "1.0.0"));
+
+		createFeatureProject("feature.a", "2.0.0", f -> {
+			addIncludedPlugin(f, "plugin.a", "1.0.0");
+		});
+		createFeatureProject("feature.a", "1.0.0", f -> {
+			addIncludedPlugin(f, "plugin.b", "1.0.0");
+		});
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "2.0.0", f -> {
+					addIncludedPlugin(f, "plugin.c", "1.0.0");
+				}), //
+				targetFeature("feature.z", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.d", "1.0.0");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		ILaunchConfigurationWorkingCopy wc = createFeatureLaunchConfig();
+		wc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:default"));
+		wc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+
+		assertGetMergedBundleMap(wc, Set.of( //
+				targetBundle("plugin.a", "1.0.0")));
+	}
+
+	@Test
+	public void testGetMergedBundleMap_featureSelectionForLocationWorkspaceButNoWorkspaceFeaturePresent_latestExternalFeature()
+			throws Throwable {
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"), //
+				bundle("plugin.b", "1.0.0"), //
+				bundle("plugin.c", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "2.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a", "1.0.0");
+				}), //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.b", "1.0.0");
+				}), //
+				targetFeature("feature.z", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.c", "1.0.0");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		ILaunchConfigurationWorkingCopy wc = createFeatureLaunchConfig();
+		wc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:default"));
+		wc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+
+		assertGetMergedBundleMap(wc, Set.of( //
+				targetBundle("plugin.a", "1.0.0")));
+	}
+
+	@Test
+	public void testGetMergedBundleMap_featureSelectionForLocationExternal_latestExternalFeature() throws Throwable {
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"), //
+				bundle("plugin.b", "1.0.0"), //
+				bundle("plugin.c", "1.0.0"), //
+				bundle("plugin.d", "1.0.0"));
+
+		createFeatureProject("feature.a", "1.0.0", f -> {
+			addIncludedPlugin(f, "plugin.a", "1.0.0");
+		});
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "2.0.0", f -> {
+					addIncludedPlugin(f, "plugin.b", "1.0.0");
+				}), //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.c", "1.0.0");
+				}), //
+				targetFeature("feature.z", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.d", "1.0.0");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		ILaunchConfigurationWorkingCopy wc = createFeatureLaunchConfig();
+		wc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:default"));
+		wc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+
+		assertGetMergedBundleMap(wc, Set.of( //
+				targetBundle("plugin.b", "1.0.0")));
+	}
+
+	@Test
+	public void testGetMergedBundleMap_featureSelectionForLocationExternalButNoExternalFeaturePresent_noFeature()
+			throws Throwable {
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"));
+
+		createFeatureProject("feature.a", "2.0.0", f -> {
+			addIncludedPlugin(f, "plugin.a", "1.0.0");
+		});
+
+		List<NameVersionDescriptor> targetFeatures = List.of();
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		ILaunchConfigurationWorkingCopy wc = createFeatureLaunchConfig();
+		wc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:default"));
+		wc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+
+		assertGetMergedBundleMap(wc, emptySet());
+	}
+
+	// --- included plug-ins ---
+
+	@Test
+	public void testGetMergedBundleMap_includedPluginWithDefaultVersion() throws Throwable {
+		createPluginProject("plugin.a", "1.0.0");
+		createPluginProject("plugin.a", "1.0.1");
+		createPluginProject("plugin.b", "1.0.0");
+
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "2.0.0"), //
+				bundle("plugin.a", "2.0.1"), //
+				bundle("plugin.c", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a", DEFAULT_VERSION);
+				}), //
+				targetFeature("feature.b", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.b", DEFAULT_VERSION);
+				}), //
+				targetFeature("feature.c", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.c", DEFAULT_VERSION);
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution explicit workspace", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.0.1")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:external"));
+
+			assertGetMergedBundleMap("pluginResolution explicit external", lc, Set.of( //
+					targetBundle("plugin.a", "2.0.1")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_WORKSPACE);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:default"));
+
+			assertGetMergedBundleMap("pluginResolution default workspace", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.0.1")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_EXTERNAL);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:default"));
+
+			assertGetMergedBundleMap("pluginResolution default external", lc, Set.of( //
+					targetBundle("plugin.a", "2.0.1")));
+		}
+		// Plug-ins only in secondary location
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.b:external"));
+
+			assertGetMergedBundleMap("pluginResolution explicit workspace", lc, Set.of( //
+					workspaceBundle("plugin.b", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.c:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution explicit external", lc, Set.of( //
+					targetBundle("plugin.c", "1.0.0")));
+		}
+	}
+
+	@Test
+	public void testGetMergedBundleMap_includedPluginWithSpecificVersion() throws Throwable {
+		createPluginProject("plugin.a", "1.0.0");
+		createPluginProject("plugin.a", "1.2.0");
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"), //
+				bundle("plugin.a", "1.1.0"), //
+				bundle("plugin.z", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a", "1.0.0");
+				}), //
+				targetFeature("feature.b", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a", "2.0.0");
+				}), //
+				targetFeature("feature.c", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a", "1.1.0");
+				}), //
+				targetFeature("feature.d", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a", "1.2.0");
+				}), //
+				targetFeature("feature.e", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a", "1.0.0.someQualifier");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		// Perfect version-match in primary location (while secondary location
+		// has matches too)
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution explicit workspace", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:external"));
+
+			assertGetMergedBundleMap("pluginResolution explicit external", lc, Set.of( //
+					targetBundle("plugin.a", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_WORKSPACE);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:default"));
+
+			assertGetMergedBundleMap("pluginResolution default workspace", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_EXTERNAL);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:default"));
+
+			assertGetMergedBundleMap("pluginResolution default external", lc, Set.of( //
+					targetBundle("plugin.a", "1.0.0")));
+		}
+		// Unqualified version-match in primary location
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.e:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution explicit workspace", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.e:external"));
+
+			assertGetMergedBundleMap("pluginResolution explicit external", lc, Set.of( //
+					targetBundle("plugin.a", "1.0.0")));
+		}
+		// no version-match at all (for included plug-ins the latest plug-in of
+		// a location is added if there is no match in the primary location)
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.b:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution workspace no match", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.2.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.b:external"));
+
+			assertGetMergedBundleMap("pluginResolution external no match", lc, Set.of( //
+					targetBundle("plugin.a", "1.1.0")));
+		}
+		// Perfect version match only in secondary location (but another version
+		// is present in the primary too).
+		// To be able to conveniently override a specific version of a plug-in,
+		// which is actually included by a (transitive) feature from the TP, by
+		// a plug-in from the workspace (which happens frequently when
+		// one has just updated the plug-in version) the latest plug-in from the
+		// primary location is taken if one is present and none matches the
+		// specified version exactly (with or without considering qualifiers).
+		// See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=576887
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.c:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution workspace but match is external", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.2.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.d:external"));
+
+			assertGetMergedBundleMap("pluginResolution external but match is in workspace", lc, Set.of( //
+					targetBundle("plugin.a", "1.1.0")));
+		}
+	}
+
+	// --- included features ---
+
+	@Test
+	public void testGetMergedBundleMap_includedFeatureWithDefaultVersion() throws Throwable {
+		// plug-in names contain version-like suffixes to have no chance to
+		// interfere with conveniences of plug-in resolution
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a.e.100", "1.0.0"), //
+				bundle("plugin.a.w.101", "1.0.0"), //
+				bundle("plugin.z", "1.0.0"));
+
+		createFeatureProject("feature.a", "1.0.3", f -> {
+			addIncludedPlugin(f, "plugin.a.w.101", "1.0.0");
+		});
+		createFeatureProject("feature.a", "1.0.0", f -> {
+			addIncludedPlugin(f, "plugin.z", "1.0.0");
+		});
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.z", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.a", DEFAULT_VERSION);
+				}), //
+
+				targetFeature("feature.a", "1.0.2", f -> {
+					addIncludedPlugin(f, "plugin.a.e.100", "1.0.0");
+				}), //
+				targetFeature("feature.a", "1.0.1", f -> {
+					addIncludedPlugin(f, "plugin.z", "1.0.0");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		{
+			ILaunchConfigurationWorkingCopy lcW = createFeatureLaunchConfig();
+			lcW.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.z:default"));
+			lcW.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+
+			assertGetMergedBundleMap("feature-location workspace", lcW, Set.of( //
+					targetBundle("plugin.a.w.101", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lcE = createFeatureLaunchConfig();
+			lcE.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.z:default"));
+			lcE.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+
+			assertGetMergedBundleMap("feature-location external", lcE, Set.of( //
+					targetBundle("plugin.a.e.100", "1.0.0")));
+		}
+	}
+
+	@Test
+	public void testGetMergedBundleMap_includedFeatureWithSpecificVersion() throws Throwable {
+		// plug-in names contain version-like suffixes to have no chance to
+		// interfere with conveniences of plug-in resolution
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a.w.100", "1.0.0"), //
+				bundle("plugin.a.w.300", "1.0.0"), //
+				bundle("plugin.a.e.100", "1.0.0"), //
+				bundle("plugin.a.e.200", "1.0.0"), //
+				bundle("plugin.a.e.400", "1.0.0"), //
+				bundle("plugin.z", "1.0.0"));
+
+		createFeatureProject("feature.a", "1.0.0", f -> {
+			addIncludedPlugin(f, "plugin.a.w.100", "1.0.0");
+		});
+		createFeatureProject("feature.a", "3.0.0", f -> {
+			addIncludedPlugin(f, "plugin.a.w.300", "1.0.0");
+		});
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a.e.100", "1.0.0");
+				}), //
+				targetFeature("feature.a", "2.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a.e.200", "1.0.0");
+				}), //
+				targetFeature("feature.a", "4.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a.e.400", "1.0.0");
+				}), //
+
+				targetFeature("feature.b", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.a", "1.0.0");
+				}), //
+				targetFeature("feature.c", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.a", "1.2.0");
+				}), //
+				targetFeature("feature.d", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.a", "2.0.0");
+				}), //
+				targetFeature("feature.e", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.a", "3.0.0");
+				}), //
+				targetFeature("feature.f", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.a", "1.0.0.someQualifier");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		// Perfect version-match in primary location (while secondary location
+		// has matches too)
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.b:default"));
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+
+			assertGetMergedBundleMap("feature-location workspace", lc, Set.of( //
+					targetBundle("plugin.a.w.100", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.b:default"));
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+
+			assertGetMergedBundleMap("feature-location external", lc, Set.of( //
+					targetBundle("plugin.a.e.100", "1.0.0")));
+		}
+		// Unqualified version-match in primary location
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.f:default"));
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+
+			assertGetMergedBundleMap("feature-location workspace", lc, Set.of( //
+					targetBundle("plugin.a.w.100", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.f:default"));
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+
+			assertGetMergedBundleMap("feature-location external", lc, Set.of( //
+					targetBundle("plugin.a.e.100", "1.0.0")));
+		}
+		// no version-match at all (for included features the latest features of
+		// a location is added if there is no match in the primary location)
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.c:default"));
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+
+			assertGetMergedBundleMap("feature-location workspace no match", lc, Set.of( //
+					targetBundle("plugin.a.w.300", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.c:default"));
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+
+			assertGetMergedBundleMap("feature-location external no match", lc, Set.of( //
+					targetBundle("plugin.a.e.400", "1.0.0")));
+		}
+		// Perfect version match only in secondary location (but another version
+		// is present in the primary too).
+		// To be able to conveniently override a specific version of a feature,
+		// which is actually included (transitively) by a feature from the TP,
+		// by a feature from the workspace (which happens frequently when
+		// one has just updated the feature version) the latest feature from the
+		// primary location is taken if one is present and none matches the
+		// specified version exactly (with or without considering qualifiers).
+		// See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=576887
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.d:default"));
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+
+			assertGetMergedBundleMap("feature-location workspace but exact match is external", lc, Set.of( //
+					targetBundle("plugin.a.w.300", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.e:default"));
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+
+			assertGetMergedBundleMap("feature-location external but exact match is in workspace", lc, Set.of(//
+					targetBundle("plugin.a.e.400", "1.0.0")));
+		}
+	}
+
+	// --- required/imported plug-in dependencies ---
+
+	@Test
+	public void testGetMergedBundleMap_requiredPluginWithNoVersion() throws Throwable {
+		createPluginProject("plugin.a", "1.0.0");
+		createPluginProject("plugin.a", "1.1.0");
+		createPluginProject("plugin.b", "1.0.0");
+
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "2.0.0"), //
+				bundle("plugin.a", "2.1.0"), //
+				bundle("plugin.c", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addRequiredPlugin(f, "plugin.a", null, IMatchRules.NONE);
+				}), //
+				targetFeature("feature.b", "1.0.0", f -> {
+					addRequiredPlugin(f, "plugin.b", null, IMatchRules.NONE);
+				}), //
+				targetFeature("feature.c", "1.0.0", f -> {
+					addRequiredPlugin(f, "plugin.c", null, IMatchRules.NONE);
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		// explicit pluginResolution location
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution explicit workspace", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.1.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:external"));
+
+			assertGetMergedBundleMap("pluginResolution explicit external", lc, Set.of( //
+					targetBundle("plugin.a", "2.1.0")));
+		}
+		// default pluginResolution location
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_WORKSPACE);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:default"));
+
+			assertGetMergedBundleMap("pluginResolution default workspace", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.1.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_EXTERNAL);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:default"));
+
+			assertGetMergedBundleMap("pluginResolution default external", lc, Set.of( //
+					targetBundle("plugin.a", "2.1.0")));
+		}
+		// Plug-ins only in secondary location
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.b:external"));
+
+			assertGetMergedBundleMap("pluginResolution external but match in workspace", lc, Set.of( //
+					workspaceBundle("plugin.b", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.c:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution workspace but match is external", lc, Set.of( //
+					targetBundle("plugin.c", "1.0.0")));
+		}
+	}
+
+	@Test
+	public void testGetMergedBundleMap_requiredPluginWithSpecificVersion1() throws Throwable {
+		createPluginProject("plugin.a", "1.0.0");
+		createPluginProject("plugin.a", "1.1.2");
+		createPluginProject("plugin.a", "2.0.0");
+
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"), //
+				bundle("plugin.a", "1.2.3"), //
+				bundle("plugin.a", "3.0.0"), //
+				bundle("plugin.z", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addRequiredPlugin(f, "plugin.a", "1.0.0", IMatchRules.NONE);
+				}), //
+				targetFeature("feature.b", "1.0.0", f -> {
+					addRequiredPlugin(f, "plugin.a", "1.0.0", IMatchRules.COMPATIBLE);
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		// MatchRule NONE/COMPATIBLE (behave the same according to VersionUtil)
+		// and location resolution tests.
+		// explicit pluginResolution location
+		{
+			for (String feature : List.of("feature.a", "feature.b")) {
+				ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+				lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of(feature + ":workspace"));
+
+				assertGetMergedBundleMap("pluginResolution explicit workspace", lc, Set.of( //
+						workspaceBundle("plugin.a", "1.1.2")));
+			}
+		}
+		{
+			for (String feature : List.of("feature.a", "feature.b")) {
+				ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+				lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of(feature + ":external"));
+
+				assertGetMergedBundleMap("pluginResolution explicit external", lc, Set.of( //
+						targetBundle("plugin.a", "1.2.3")));
+			}
+		}
+		// default pluginResolution location
+		{
+			for (String feature : List.of("feature.a", "feature.b")) {
+				ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+				lc.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION,
+						IPDELauncherConstants.LOCATION_WORKSPACE);
+				lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of(feature + ":default"));
+
+				assertGetMergedBundleMap("pluginResolution default workspace", lc, Set.of( //
+						workspaceBundle("plugin.a", "1.1.2")));
+			}
+		}
+		{
+			for (String feature : List.of("feature.a", "feature.b")) {
+				ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+				lc.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION,
+						IPDELauncherConstants.LOCATION_EXTERNAL);
+				lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of(feature + ":default"));
+
+				assertGetMergedBundleMap("pluginResolution default external", lc, Set.of( //
+						targetBundle("plugin.a", "1.2.3")));
+			}
+		}
+	}
+
+	@Test
+	public void testGetMergedBundleMap_requiredPluginWithSpecificVersion2() throws Throwable {
+		createPluginProject("plugin.a", "1.0.0");
+		createPluginProject("plugin.a", "1.0.1");
+		createPluginProject("plugin.a", "1.1.0");
+		createPluginProject("plugin.a", "1.1.2");
+		createPluginProject("plugin.a", "2.0.0");
+
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"), //
+				bundle("plugin.a", "1.0.2"), //
+				bundle("plugin.a", "1.2.0"), //
+				bundle("plugin.a", "1.2.3"), //
+				bundle("plugin.a", "3.0.0"), //
+				bundle("plugin.z", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.c", "1.0.0", f -> {
+					addRequiredPlugin(f, "plugin.a", "1.0.0", IMatchRules.EQUIVALENT);
+				}), //
+				targetFeature("feature.d", "1.0.0", f -> {
+					addRequiredPlugin(f, "plugin.a", "1.1.0", IMatchRules.EQUIVALENT);
+				}), //
+				targetFeature("feature.e", "1.0.0", f -> {
+					addRequiredPlugin(f, "plugin.a", "1.2.0", IMatchRules.EQUIVALENT);
+				}), //
+				targetFeature("feature.f", "1.0.0", f -> {
+					addRequiredPlugin(f, "plugin.a", "8.0.0", IMatchRules.EQUIVALENT);
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		// No need to test all match-rules. Just have to check that version
+		// match rules are obeyed. For the following cases match-rule EQUIVALENT
+		// is used to check different per-location-availability scenarios.
+
+		// match in primary location (while secondary location has matches too)
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.c:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution workspace", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.0.1")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.c:external"));
+
+			assertGetMergedBundleMap("pluginResolution external", lc, Set.of( //
+					targetBundle("plugin.a", "1.0.2")));
+		}
+		// match only in secondary location
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.d:external"));
+
+			assertGetMergedBundleMap("pluginResolution external but match in workspace", lc, Set.of( //
+					workspaceBundle("plugin.a", "1.1.2")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.e:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution workspace but match is external", lc, Set.of( //
+					targetBundle("plugin.a", "1.2.3")));
+		}
+		// no match at all
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.f:external"));
+
+			assertGetMergedBundleMap("pluginResolution external no match", lc, Set.of());
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.f:workspace"));
+
+			assertGetMergedBundleMap("pluginResolution workspace no match", lc, Set.of());
+		}
+	}
+
+	// --- required/imported feature dependencies ---
+
+	@Test
+	public void testGetMergedBundleMap_requiredFeatureWithNoVersion() throws Throwable {
+		// plug-in names contain version-like suffixes to have no chance to
+		// interfere with conveniences of plug-in resolution
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a.w.100", "1.0.0"), //
+				bundle("plugin.a.w.110", "1.0.0"), //
+				bundle("plugin.a.e.200", "1.0.0"), //
+				bundle("plugin.a.e.210", "1.0.0"), //
+				bundle("plugin.b.w.100", "1.0.0"), //
+				bundle("plugin.c.e.100", "1.0.0"), //
+				bundle("plugin.xyz", "1.0.0"));
+
+		createFeatureProject("feature.a", "1.0.0", f -> {
+			addIncludedPlugin(f, "plugin.a.w.100", "1.0.0");
+		});
+		createFeatureProject("feature.a", "1.1.0", f -> {
+			addIncludedPlugin(f, "plugin.a.w.110", "1.0.0");
+		});
+		createFeatureProject("feature.b", "1.0.0", f -> {
+			addIncludedPlugin(f, "plugin.b.w.100", "1.0.0");
+		});
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.z", "1.0.0", f -> {
+					addRequiredFeature(f, "feature.a", null, IMatchRules.NONE);
+				}), //
+				targetFeature("feature.y", "1.0.0", f -> {
+					addRequiredFeature(f, "feature.b", null, IMatchRules.NONE);
+				}), //
+				targetFeature("feature.x", "1.0.0", f -> {
+					addRequiredFeature(f, "feature.c", null, IMatchRules.NONE);
+				}), //
+
+				targetFeature("feature.a", "2.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a.e.200", "1.0.0");
+				}), //
+				targetFeature("feature.a", "2.1.0", f -> {
+					addIncludedPlugin(f, "plugin.a.e.210", "1.0.0");
+				}), //
+				targetFeature("feature.c", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.c.e.100", "1.0.0");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.z:default"));
+
+			assertGetMergedBundleMap("featureResolution explicit workspace", lc, Set.of( //
+					targetBundle("plugin.a.w.110", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.z:default"));
+
+			assertGetMergedBundleMap("featureResolution explicit external", lc, Set.of( //
+					targetBundle("plugin.a.e.210", "1.0.0")));
+		}
+		// Features only in secondary location
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.y:default"));
+
+			assertGetMergedBundleMap("featureResolution external but match in workspace", lc, Set.of());
+			// if featureResolution is 'external', workspace features are not
+			// considered at all
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.x:default"));
+
+			assertGetMergedBundleMap("featureResolution workspace but match is external", lc, Set.of( //
+					targetBundle("plugin.c.e.100", "1.0.0")));
+		}
+	}
+
+	@Test
+	public void testGetMergedBundleMap_requiredFeatureWithSpecificVersion1() throws Throwable {
+		// plug-in names contain version-like suffixes to have no chance to
+		// interfere with conveniences of plug-in resolution
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a.w.100", "1.0.0"), //
+				bundle("plugin.a.w.110", "1.0.0"), //
+				bundle("plugin.a.w.200", "1.0.0"), //
+				bundle("plugin.a.e.100", "1.0.0"), //
+				bundle("plugin.a.e.123", "1.0.0"), //
+				bundle("plugin.a.e.300", "1.0.0"), //
+				bundle("plugin.xyz", "1.0.0"));
+
+		createFeatureProject("feature.a", "1.0.0", f -> {
+			addIncludedPlugin(f, "plugin.a.w.100", "1.0.0");
+		});
+		createFeatureProject("feature.a", "1.1.0", f -> {
+			addIncludedPlugin(f, "plugin.a.w.110", "1.0.0");
+		});
+		createFeatureProject("feature.a", "2.0.0", f -> {
+			addIncludedPlugin(f, "plugin.a.w.200", "1.0.0");
+		});
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.z", "1.0.0", f -> {
+					addRequiredFeature(f, "feature.a", "1.0.0", IMatchRules.NONE);
+				}), //
+				targetFeature("feature.y", "1.0.0", f -> {
+					addRequiredFeature(f, "feature.a", "1.0.0", IMatchRules.COMPATIBLE);
+				}), //
+
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a.e.100", "1.0.0");
+				}), //
+				targetFeature("feature.a", "1.2.3", f -> {
+					addIncludedPlugin(f, "plugin.a.e.123", "1.0.0");
+				}), //
+				targetFeature("feature.a", "3.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a.e.300", "1.0.0");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		// MatchRule NONE/COMPATIBLE (behave the same according to VersionUtil)
+		// and location resolution tests.
+		// explicit pluginResolution location
+		{
+			for (String feature : List.of("feature.z", "feature.y")) {
+				ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+				lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION,
+						IPDELauncherConstants.LOCATION_WORKSPACE);
+				lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of(feature + ":default"));
+
+				assertGetMergedBundleMap("pluginResolution explicit workspace", lc, Set.of( //
+						targetBundle("plugin.a.w.110", "1.0.0")));
+			}
+		}
+		{
+			for (String feature : List.of("feature.z", "feature.y")) {
+				ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+				lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION,
+						IPDELauncherConstants.LOCATION_EXTERNAL);
+				lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of(feature + ":default"));
+
+				assertGetMergedBundleMap("pluginResolution explicit external", lc, Set.of( //
+						targetBundle("plugin.a.e.123", "1.0.0")));
+			}
+		}
+	}
+
+	@Test
+	public void testGetMergedBundleMap_requiredFeatureWithSpecificVersion2() throws Throwable {
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a.w.100", "1.0.0"), //
+				bundle("plugin.a.w.101", "1.0.0"), //
+				bundle("plugin.a.w.110", "1.0.0"), //
+				bundle("plugin.a.w.112", "1.0.0"), //
+				bundle("plugin.a.w.200", "1.0.0"), //
+				bundle("plugin.a.e.100", "1.0.0"), //
+				bundle("plugin.a.e.102", "1.0.0"), //
+				bundle("plugin.a.e.120", "1.0.0"), //
+				bundle("plugin.a.e.123", "1.0.0"), //
+				bundle("plugin.a.e.300", "1.0.0"), //
+				bundle("plugin.xyz", "1.0.0"));
+
+		createFeatureProject("feature.a", "1.0.0", f -> {
+			addIncludedPlugin(f, "plugin.a.w.100", "1.0.0");
+		});
+		createFeatureProject("feature.a", "1.0.1", f -> {
+			addIncludedPlugin(f, "plugin.a.w.101", "1.0.0");
+		});
+		createFeatureProject("feature.a", "1.1.0", f -> {
+			addIncludedPlugin(f, "plugin.a.w.110", "1.0.0");
+		});
+		createFeatureProject("feature.a", "1.1.2", f -> {
+			addIncludedPlugin(f, "plugin.a.w.112", "1.0.0");
+		});
+		createFeatureProject("feature.a", "2.0.0", f -> {
+			addIncludedPlugin(f, "plugin.a.w.200", "1.0.0");
+		});
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.z", "1.0.0", f -> {
+					addRequiredFeature(f, "feature.a", "1.0.0", IMatchRules.EQUIVALENT);
+				}), //
+				targetFeature("feature.y", "1.0.0", f -> {
+					addRequiredFeature(f, "feature.a", "1.1.0", IMatchRules.EQUIVALENT);
+				}), //
+				targetFeature("feature.x", "1.0.0", f -> {
+					addRequiredFeature(f, "feature.a", "1.2.0", IMatchRules.EQUIVALENT);
+				}), //
+				targetFeature("feature.w", "1.0.0", f -> {
+					addRequiredFeature(f, "feature.a", "8.0.0", IMatchRules.EQUIVALENT);
+				}), //
+
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a.e.100", "1.0.0");
+				}), //
+				targetFeature("feature.a", "1.0.2", f -> {
+					addIncludedPlugin(f, "plugin.a.e.102", "1.0.0");
+				}), //
+				targetFeature("feature.a", "1.2.0", f -> {
+					addIncludedPlugin(f, "plugin.a.e.120", "1.0.0");
+				}), //
+				targetFeature("feature.a", "1.2.3", f -> {
+					addIncludedPlugin(f, "plugin.a.e.123", "1.0.0");
+				}), //
+				targetFeature("feature.a", "3.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a.e.300", "1.0.0");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		// No need to test all match-rules. Just have to check that version
+		// match rules are obeyed. For the following cases match-rule EQUIVALENT
+		// is used to check different per-location-availability scenarios.
+
+		// match in primary location (while secondary location has matches too)
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.z:default"));
+
+			assertGetMergedBundleMap("featureResolution workspace", lc, Set.of( //
+					targetBundle("plugin.a.w.101", "1.0.0")));
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.z:default"));
+
+			assertGetMergedBundleMap("featureResolution external", lc, Set.of( //
+					targetBundle("plugin.a.e.102", "1.0.0")));
+		}
+		// match only in secondary location
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.y:default"));
+
+			assertGetMergedBundleMap("featureResolution external but match in workspace", lc, Set.of());
+			// if featureResolution is 'external', workspace features are not
+			// considered at all
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.x:default"));
+
+			assertGetMergedBundleMap("featureResolution workspace but match is external", lc, Set.of( //
+					targetBundle("plugin.a.e.123", "1.0.0")));
+		}
+		// no match at all
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_EXTERNAL);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.w:default"));
+
+			assertGetMergedBundleMap("featureResolution external no match", lc, Set.of());
+		}
+		{
+			ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+			lc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+			lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.w:default"));
+
+			assertGetMergedBundleMap("featureResolution workspace no match", lc, Set.of());
+		}
+	}
+
+	// --- miscellaneous cases ---
+
+	@Test
+	public void testGetMergedBundleMap_includedPluginAndFeatureEnvironmentNotMatchingTargetEnvironment()
+			throws Throwable {
+		String thisOS = Platform.getOS();
+		String otherOS = thisOS.equals(Platform.OS_LINUX) ? Platform.OS_WIN32 : Platform.OS_LINUX;
+
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"), //
+				bundle("plugin.b", "1.0.0"), //
+				bundle("plugin.c", "1.0.0"), //
+				bundle("plugin.d", "1.0.0"), //
+				bundle("plugin.z", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.c", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.c", "1.0.0");
+				}), //
+				targetFeature("feature.d", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.d", "1.0.0");
+				}), //
+
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a", "1.0.0").setOS(thisOS);
+					addIncludedPlugin(f, "plugin.b", "1.0.0").setOS(otherOS);
+
+					addIncludedFeature(f, "feature.c", "1.0.0").setOS(thisOS);
+					addIncludedFeature(f, "feature.d", "1.0.0").setOS(otherOS);
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+		// No need to test all aspects of environment matches, just test if
+		// environment is considered.
+
+		ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+		lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:external"));
+
+		assertGetMergedBundleMap("pluginResolution explicit workspace", lc, Set.of( //
+				targetBundle("plugin.a", "1.0.0"), //
+				targetBundle("plugin.c", "1.0.0")));
+	}
+
+	@Test
+	public void testGetMergedBundleMap_featureEnvironmentNotMatchingTargetEnvironment() throws Throwable {
+		String thisOS = Platform.getOS();
+		String otherOS = thisOS.equals(Platform.OS_LINUX) ? Platform.OS_WIN32 : Platform.OS_LINUX;
+
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.b", "1.0.0"), //
+				bundle("plugin.b", "1.1.0"), //
+				bundle("plugin.c", "1.0.0"), //
+				bundle("plugin.c", "1.1.0"), //
+				bundle("plugin.z", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.b", "1.0.0", f -> {
+					f.setOS(thisOS);
+					addIncludedPlugin(f, "plugin.b", "1.0.0");
+				}), //
+				targetFeature("feature.b", "1.1.0", f -> {
+					f.setOS(otherOS);
+					addIncludedPlugin(f, "plugin.b", "1.1.0");
+				}), //
+				targetFeature("feature.c", "1.0.0", f -> {
+					f.setOS(thisOS);
+					addIncludedPlugin(f, "plugin.c", "1.0.0");
+				}), //
+				targetFeature("feature.c", "1.1.0", f -> {
+					f.setOS(otherOS);
+					addIncludedPlugin(f, "plugin.c", "1.1.0");
+				}), //
+
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.b", DEFAULT_VERSION);
+					addRequiredFeature(f, "feature.c", "1.0.0", IMatchRules.COMPATIBLE);
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+		// No need to test all aspects of environment matches, just test if
+		// environment is considered.
+
+		ILaunchConfigurationWorkingCopy lc = createFeatureLaunchConfig();
+		lc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of("feature.a:external"));
+
+		assertGetMergedBundleMap("pluginResolution explicit workspace", lc, Set.of( //
+				targetBundle("plugin.b", "1.0.0"), //
+				targetBundle("plugin.c", "1.0.0")));
+	}
+
+	@Test
+	public void testGetMergedBundleMap_multipleInclusionOfPluginAndFeature() throws Throwable {
+
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"), //
+				bundle("plugin.b", "1.0.0"), //
+				bundle("plugin.z", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a", "1.0.0");
+					addRequiredPlugin(f, "plugin.a", "1.0.0", IMatchRules.PERFECT);
+				}), //
+				targetFeature("feature.b", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.a", "1.0.0");
+					addRequiredPlugin(f, "plugin.a", "1.0.0", IMatchRules.PERFECT);
+				}), //
+				targetFeature("feature.c", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.z", "1.0.0");
+					addRequiredFeature(f, "feature.z", "1.0.0", IMatchRules.PERFECT);
+				}), //
+				targetFeature("feature.d", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.z", "1.0.0");
+					addRequiredFeature(f, "feature.z", "1.0.0", IMatchRules.PERFECT);
+				}), //
+
+				targetFeature("feature.z", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.z", "1.0.0");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		ILaunchConfigurationWorkingCopy wc = createFeatureLaunchConfig();
+		wc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of( //
+				"feature.a:default", //
+				"feature.b:default", //
+				"feature.c:default", //
+				"feature.d:default"));
+
+		assertGetMergedBundleMap(wc, Set.of( //
+				targetBundle("plugin.a", "1.0.0"), //
+				targetBundle("plugin.z", "1.0.0")));
+	}
+
+	@Test
+	public void testGetMergedBundleMap_additionalPlugins() throws Throwable {
+		createPluginProject("plugin.a", "1.0.0");
+		createPluginProject("plugin.b", "1.0.0");
+		createPluginProject("plugin.d", "1.0.0");
+		createPluginProject("plugin.e", "1.0.0");
+
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"), //
+				bundle("plugin.b", "1.0.0"), //
+				bundle("plugin.c", "1.0.0"), //
+				bundle("plugin.d", "1.0.0"), //
+				bundle("plugin.e", "1.0.0"), //
+				bundle("plugin.f", "2.0.0"), //
+				bundle("plugin.z", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.d", "1.0.0");
+				}), //
+				targetFeature("feature.b", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.e", "1.0.0");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		ILaunchConfigurationWorkingCopy wc = createFeatureLaunchConfig();
+		wc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of( //
+				"feature.a:external", //
+				"feature.b:workspace"));
+		wc.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_EXTERNAL);
+		wc.setAttribute(IPDELauncherConstants.ADDITIONAL_PLUGINS, Set.of( //
+				// id:version:location:enabled:startLeval:autoStart
+				"plugin.a:1.0.0:default:true:1:true", //
+				"plugin.b:1.0.0:workspace:true:2:true", //
+				"plugin.c:1.0.0:workspace:true:3:true", //
+				"plugin.d:1.0.0:external:true:4:true", // overwrite from feature
+				"plugin.e:1.0.0:external:true:5:true", // attempted overwrite
+				"plugin.f:1.0.0:default:true:6:true", // not matching version
+				"plugin.z:1.0.0:external:false:7:true") // disabled
+				);
+		// overwriting the plug-in also included by a feature only works
+		// if the same primary location is used and both pull in the
+		// same version. Otherwise two different bundles are added
+
+		assertGetMergedBundleMap(wc, Map.of( //
+				targetBundle("plugin.a", "1.0.0"), "1:true", //
+				workspaceBundle("plugin.b", "1.0.0"), "2:true", //
+				targetBundle("plugin.c", "1.0.0"), "3:true", //
+				targetBundle("plugin.d", "1.0.0"), "4:true", //
+				targetBundle("plugin.e", "1.0.0"), "5:true", //
+				workspaceBundle("plugin.e", "1.0.0"), "default:default", //
+				targetBundle("plugin.f", "2.0.0"), "6:true"));
+	}
+
+	@Test
+	public void testGetMergedBundleMap_inheritanceOfPluginResolution() throws Throwable {
+		createPluginProject("plugin.a", "1.0.0");
+		createPluginProject("plugin.b", "1.0.0");
+		createPluginProject("plugin.d", "1.0.0");
+		createPluginProject("plugin.e", "1.0.0");
+
+		List<NameVersionDescriptor> targetBundles = List.of( //
+				bundle("plugin.a", "1.0.0"), //
+				bundle("plugin.b", "1.0.0"), //
+				bundle("plugin.c", "1.0.0"), //
+				bundle("plugin.d", "1.0.0"), //
+				bundle("plugin.e", "1.0.0"), //
+				bundle("plugin.z", "1.0.0"));
+
+		List<NameVersionDescriptor> targetFeatures = List.of( //
+				targetFeature("feature.a", "1.0.0", f -> {
+					addIncludedFeature(f, "feature.b", "1.0.0");
+					addRequiredFeature(f, "feature.c", "", IMatchRules.COMPATIBLE);
+					addIncludedFeature(f, "feature.d", "1.0.0");
+				}), //
+				targetFeature("feature.b", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.b", "1.0.0");
+				}), //
+				targetFeature("feature.c", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.c", "1.0.0");
+				}), //
+				targetFeature("feature.d", "1.0.0", f -> {
+					addIncludedPlugin(f, "plugin.d", "1.0.0");
+				}));
+
+		setTargetPlatform(targetBundles, targetFeatures);
+
+		ILaunchConfigurationWorkingCopy wc = createFeatureLaunchConfig();
+		wc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, Set.of( //
+				"feature.a:external", //
+				"feature.d:workspace"));
+
+		assertGetMergedBundleMap(wc, Set.of( //
+				targetBundle("plugin.b", "1.0.0"), //
+				targetBundle("plugin.c", "1.0.0"), //
+				workspaceBundle("plugin.d", "1.0.0")));
+	}
+
+	// --- utility methods ---
+
+	private static interface CoreConsumer<E> {
+		void accept(E e) throws CoreException;
+	}
+
+	private static void createFeatureProject(String id, String version, CoreConsumer<IFeature> featureSetup)
+			throws Throwable {
+		createFeature(id, version, id + "_" + version.replace('.', '_'), featureSetup);
+	}
+
+	private static IFeature createFeature(String id, String version, String projectName,
+			CoreConsumer<IFeature> featureSetup) throws Throwable {
+		FeatureData featureData = new FeatureData();
+		featureData.id = id;
+		featureData.version = version;
+
+		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+		IProject project = workspaceRoot.getProject(projectName);
+		IPath location = workspaceRoot.getLocation().append(project.getName());
+
+		IRunnableWithProgress operation = new AbstractCreateFeatureOperation(project, location, featureData, null) {
+			@Override
+			protected void configureFeature(IFeature feature, WorkspaceFeatureModel model) throws CoreException {
+				featureSetup.accept(feature);
+			}
+
+			@Override
+			protected void openFeatureEditor(IFile manifestFile) {
+				// don't open in headless tests
+			}
+		};
+		operation.run(new NullProgressMonitor());
+		FeatureModelManager featureModelManager = PDECore.getDefault().getFeatureModelManager();
+		return featureModelManager.getFeatureModel(project).getFeature();
+	}
+
+	// Created Feature-projects get reloaded shortly after their creation, in
+	// the end of the auto-build job (due to some pending resource-changed
+	// events). In the beginning of the reload the feature model is reset and
+	// all fields become null/0. So if the model is read inbetween the model
+	// state could be inconsistent. This creates a race condition, which
+	// occasionally leads to test-failure. All my attempts to consume all
+	// resource-change events immediately to resolve the race-condition failed.
+	// I also tried to await the World-Changed event fired on a FeatureModel
+	// once it was reloaded but then the test spent most of its runtime waiting.
+	// Blocking all other operations was the simplest and fastest solution.
+	@BeforeClass
+	public static void acquireWorkspaceRuleToAvoidFeatureReloadByAutobuild() {
+		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+		Job.getJobManager().beginRule(root, null);
+	}
+
+	@AfterClass
+	public static void releaseWorkspaceRule() {
+		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+		Job.getJobManager().endRule(root);
+	}
+
+	private static void addRequiredPlugin(IFeature feature, String id, String version, int matchRule)
+			throws CoreException {
+		addImport(feature, id, version, matchRule, IFeatureImport.PLUGIN);
+	}
+
+	private static void addRequiredFeature(IFeature feature, String id, String version, int matchRule)
+			throws CoreException {
+		addImport(feature, id, version, matchRule, IFeatureImport.FEATURE);
+	}
+
+	private static void addImport(IFeature feature, String id, String version, int matchRule, int type)
+			throws CoreException {
+		IFeatureModelFactory factory = feature.getModel().getFactory();
+		IFeatureImport featureImport = factory.createImport();
+		featureImport.setId(id);
+		featureImport.setVersion(version);
+		featureImport.setMatch(matchRule);
+		featureImport.setType(type);
+
+		feature.addImports(new IFeatureImport[] { featureImport });
+	}
+
+	private static FeatureChild addIncludedFeature(IFeature feature, String id, String version) throws CoreException {
+		FeatureChild featureChild = (FeatureChild) feature.getModel().getFactory().createChild();
+		featureChild.setId(id);
+		featureChild.setVersion(version);
+		featureChild.setOptional(false);
+		feature.addIncludedFeatures(new IFeatureChild[] { featureChild });
+		return featureChild;
+	}
+
+	private static IFeaturePlugin addIncludedPlugin(IFeature feature, String id, String version) throws CoreException {
+		IFeaturePlugin featurePlugin = feature.getModel().getFactory().createPlugin();
+		featurePlugin.setId(id);
+		featurePlugin.setVersion(version);
+		featurePlugin.setUnpack(false);
+		feature.addPlugins(new IFeaturePlugin[] { featurePlugin });
+		return featurePlugin;
+	}
+
+	private NameVersionDescriptor targetFeature(String featureId, String featureVersion,
+			CoreConsumer<IFeature> featureSetup) throws Throwable {
+
+		IFeature feature = createFeature(featureId, featureVersion, "tp-feature-temp-project", featureSetup);
+
+		WorkspaceFeatureModel model = (WorkspaceFeatureModel) feature.getModel();
+		IResource resource = model.getUnderlyingResource();
+
+		Path featureDirectory = tpJarDirectory.resolve(Path.of("features", featureId + "_" + featureVersion));
+		Files.createDirectories(featureDirectory);
+		Path featureFile = featureDirectory.resolve(resource.getProjectRelativePath().toString());
+		try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(featureFile));) {
+			model.save(writer);
+		}
+		IProject project = resource.getProject();
+		project.delete(IResource.FORCE | IResource.ALWAYS_DELETE_PROJECT_CONTENT, null);
+
+		return new NameVersionDescriptor(featureId, featureVersion, NameVersionDescriptor.TYPE_FEATURE);
+	}
+
+	private void setTargetPlatform(List<NameVersionDescriptor> targetPlugins,
+			List<NameVersionDescriptor> targetFeatures) throws Exception {
+		TargetPlatformUtil.setDummyBundlesAsTarget(targetPlugins, tpJarDirectory);
+	}
+
+	private static ILaunchConfigurationWorkingCopy createFeatureLaunchConfig() throws CoreException {
+		String name = "feature-based-Eclipse-app";
+		ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
+		ILaunchConfigurationType type = launchManager.getLaunchConfigurationType("org.eclipse.pde.ui.RuntimeWorkbench");
+		ILaunchConfigurationWorkingCopy wc = type.newInstance(null, name);
+		wc.setAttribute(IPDELauncherConstants.USE_CUSTOM_FEATURES, true);
+		wc.setAttribute(IPDELauncherConstants.USE_DEFAULT, false);
+		wc.setAttribute(IPDELauncherConstants.FEATURE_DEFAULT_LOCATION, IPDELauncherConstants.LOCATION_WORKSPACE);
+		wc.setAttribute(IPDELauncherConstants.FEATURE_PLUGIN_RESOLUTION, IPDELauncherConstants.LOCATION_WORKSPACE);
+		return wc;
+	}
+
+	private static void assertGetMergedBundleMap(ILaunchConfiguration launchConfig,
+			Set<BundleLocationDescriptor> expectedBundles) throws Exception {
+		assertGetMergedBundleMap(null, launchConfig, expectedBundles);
+	}
+
+	private static void assertGetMergedBundleMap(String message, ILaunchConfiguration launchConfig,
+			Set<BundleLocationDescriptor> expectedBundles) throws Exception {
+
+		Map<BundleLocationDescriptor, String> expectedBundleMap = expectedBundles.stream()
+				.collect(Collectors.toMap(b -> b, b -> "default:default"));
+		assertGetMergedBundleMap(message, launchConfig, expectedBundleMap);
+	}
+
+	private static void assertGetMergedBundleMap(ILaunchConfiguration launchConfig,
+			Map<BundleLocationDescriptor, String> expectedBundleMap) throws Exception {
+		assertGetMergedBundleMap(null, launchConfig, expectedBundleMap);
+	}
+
+	private static void assertGetMergedBundleMap(String message, ILaunchConfiguration launchConfig,
+			Map<BundleLocationDescriptor, String> expectedBundleMap) throws Exception {
+
+		Map<IPluginModelBase, String> bundleMap = BundleLauncherHelper.getMergedBundleMap(launchConfig, false);
+
+		Map<IPluginModelBase, String> expectedPluginMap = new HashMap<>();
+		expectedBundleMap.forEach((pd, start) -> {
+			expectedPluginMap.put(pd.findModel(), start);
+		});
+
+		assertPluginMapsEquals(message, expectedPluginMap, bundleMap);
 	}
 }
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/LaunchConfigurationMigrationTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/LaunchConfigurationMigrationTest.java
index 8b57ff6..604baa1 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/LaunchConfigurationMigrationTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/LaunchConfigurationMigrationTest.java
@@ -47,13 +47,12 @@
 
 		assertOldPropertiesRemoved(wc);
 
-		Map<IPluginModelBase, String> workspaceBundles = BundleLauncherHelper.getWorkspaceBundleMap(wc);
-		assertEquals("default:true", workspaceBundles.get(findWorkspaceModel("org.eclipse.pde.plugin1", null)));
-		assertEquals("3:false", workspaceBundles.get(findWorkspaceModel("org.eclipse.pde.plugin2", null)));
+		Map<IPluginModelBase, String> bundles = BundleLauncherHelper.getAllSelectedPluginBundles(wc);
+		assertEquals("default:true", bundles.get(findWorkspaceModel("org.eclipse.pde.plugin1", null)));
+		assertEquals("3:false", bundles.get(findWorkspaceModel("org.eclipse.pde.plugin2", null)));
 
-		Map<IPluginModelBase, String> targetBundles = BundleLauncherHelper.getTargetBundleMap(wc);
-		assertEquals("default:true", targetBundles.get(findTargetModel("org.eclipse.core.runtime", null)));
-		assertEquals("2:false", targetBundles.get(findTargetModel("org.eclipse.ui", null)));
+		assertEquals("default:true", bundles.get(findTargetModel("org.eclipse.core.runtime", null)));
+		assertEquals("2:false", bundles.get(findTargetModel("org.eclipse.ui", null)));
 	}
 
 	@Test
@@ -66,12 +65,11 @@
 
 		assertOldPropertiesRemoved(wc);
 
-		Map<IPluginModelBase, String> workspaceBundles = BundleLauncherHelper.getWorkspaceBundleMap(wc);
-		assertEquals("default:default", workspaceBundles.get(findWorkspaceModel("org.eclipse.pde.plugin1", null)));
+		Map<IPluginModelBase, String> bundles = BundleLauncherHelper.getAllSelectedPluginBundles(wc);
+		assertEquals("default:default", bundles.get(findWorkspaceModel("org.eclipse.pde.plugin1", null)));
 
-		Map<IPluginModelBase, String> targetBundles = BundleLauncherHelper.getTargetBundleMap(wc);
-		assertEquals("default:true", targetBundles.get(findTargetModel("org.eclipse.core.runtime", null)));
-		assertEquals("2:false", targetBundles.get(findTargetModel("org.eclipse.ui", null)));
+		assertEquals("default:true", bundles.get(findTargetModel("org.eclipse.core.runtime", null)));
+		assertEquals("2:false", bundles.get(findTargetModel("org.eclipse.ui", null)));
 	}
 
 	@Test
@@ -84,13 +82,12 @@
 
 		assertOldOsgiPropertiesRemoved(wc);
 
-		Map<IPluginModelBase, String> workspaceBundles = BundleLauncherHelper.getWorkspaceBundleMap(wc);
-		assertEquals("default:true", workspaceBundles.get(findWorkspaceModel("org.eclipse.pde.plugin1", null)));
-		assertEquals("3:false", workspaceBundles.get(findWorkspaceModel("org.eclipse.pde.plugin2", null)));
+		Map<IPluginModelBase, String> bundles = BundleLauncherHelper.getAllSelectedPluginBundles(wc);
+		assertEquals("default:true", bundles.get(findWorkspaceModel("org.eclipse.pde.plugin1", null)));
+		assertEquals("3:false", bundles.get(findWorkspaceModel("org.eclipse.pde.plugin2", null)));
 
-		Map<IPluginModelBase, String> targetBundles = BundleLauncherHelper.getTargetBundleMap(wc);
-		assertEquals("default:true", targetBundles.get(findTargetModel("org.eclipse.core.runtime", null)));
-		assertEquals("2:false", targetBundles.get(findTargetModel("org.eclipse.ui", null)));
+		assertEquals("default:true", bundles.get(findTargetModel("org.eclipse.core.runtime", null)));
+		assertEquals("2:false", bundles.get(findTargetModel("org.eclipse.ui", null)));
 	}
 
 	@SuppressWarnings("deprecation")
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java
index 58e69c3..d61e4c9 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2021, 2021 Hannes Wellmann and others.
+ *  Copyright (c) 2021, 2022 Hannes Wellmann and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -15,7 +15,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import java.io.IOException;
 import java.nio.file.Path;
 import java.util.*;
 import java.util.function.Consumer;
@@ -236,7 +235,6 @@
 		};
 
 		Set<BundleLocationDescriptor> expectedBundles = Set.of( //
-				workspaceBundle("plugin.a", "1.0.0"), //
 				workspaceBundle("plugin.a", "2.0.0"));
 
 		assertGetMergedBundleMap(workspacePlugins, targetPlatformBundles, launchConfigSetup, expectedBundles);
@@ -549,7 +547,6 @@
 			wc.setAttribute(IPDELauncherConstants.SELECTED_TARGET_BUNDLES, Set.of("plugin.b"));
 		};
 		Set<BundleLocationDescriptor> expectedBundles = Set.of(//
-				targetBundle("plugin.b", "1.0.0"), //
 				targetBundle("plugin.b", "2.0.0"));
 
 		assertGetMergedBundleMap(workspacePlugins, targetPlatformBundles, launchConfigSetup, expectedBundles);
@@ -586,12 +583,11 @@
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
 			wc.setAttribute(IPDELauncherConstants.SELECTED_TARGET_BUNDLES,
-					Set.of("plugin.a*1.0.0.2020", "plugin.a*1.0.0.2021"));
-		};
+					new LinkedHashSet<>(List.of("plugin.a*1.0.0.2020", "plugin.a*1.0.0.2021")));
+		}; // first entry is selected -> LinkedHashSet ensures its the same
 
 		Set<BundleLocationDescriptor> expectedBundles = Set.of( //
-				targetBundle("plugin.a", "1.0.0.2020"), //
-				targetBundle("plugin.a", "1.0.0.2021"));
+				targetBundle("plugin.a", "1.0.0.2020"));
 
 		assertGetMergedBundleMap(workspacePlugins, targetPlatformBundles, launchConfigSetup, expectedBundles);
 	}
@@ -615,7 +611,9 @@
 
 		Set<BundleLocationDescriptor> expectedBundles = Set.of( //
 				workspaceBundle("plugin.a", "1.0.0"), //
-				workspaceBundle("plugin.b", "1.0.0"));
+				workspaceBundle("plugin.b", "1.0.0"), //
+				targetBundle("plugin.a", "1.0.1"), //
+				targetBundle("plugin.b", "2.0.0"));
 
 		assertGetMergedBundleMap(workspacePlugins, targetPlatformBundles, launchConfigSetup, expectedBundles);
 	}
@@ -637,7 +635,9 @@
 
 		Set<BundleLocationDescriptor> expectedBundles = Set.of( //
 				workspaceBundle("plugin.a", "1.0.0"), //
-				workspaceBundle("plugin.b", "2.0.0"));
+				workspaceBundle("plugin.b", "2.0.0"), //
+				targetBundle("plugin.a", "1.0.1"), //
+				targetBundle("plugin.b", "3.0.0"));
 
 		assertGetMergedBundleMap(workspacePlugins, targetPlatformBundles, launchConfigSetup, expectedBundles);
 	}
@@ -655,7 +655,8 @@
 		};
 
 		Set<BundleLocationDescriptor> expectedBundles = Set.of( //
-				workspaceBundle("plugin.a", "1.0.0"));
+				workspaceBundle("plugin.a", "1.0.0"), //
+				targetBundle("plugin.a", "1.0.2"));
 
 		assertGetMergedBundleMap(workspacePlugins, targetPlatformBundles, launchConfigSetup, expectedBundles);
 	}
@@ -702,7 +703,9 @@
 
 		Set<BundleLocationDescriptor> expectedBundles = Set.of( //
 				workspaceBundle("plugin.a", "1.0.0"), //
-				workspaceBundle("plugin.b", "1.0.0"));
+				workspaceBundle("plugin.b", "1.0.0"), //
+				targetBundle("plugin.a", "1.0.1"), //
+				targetBundle("plugin.b", "1.0.1"));
 
 		assertGetMergedBundleMap(workspacePlugins, targetPlatformBundles, launchConfigSetup, expectedBundles);
 	}
@@ -739,7 +742,7 @@
 
 		IPluginModelBase plugin = workspaceBundle("plugin.a", "1.0.0").findModel();
 
-		String entry = BundleLauncherHelper.writeBundleEntry(plugin, null, null);
+		String entry = BundleLauncherHelper.formatBundleEntry(plugin, null, null);
 		assertEquals("plugin.a", entry);
 	}
 
@@ -754,7 +757,7 @@
 
 		IPluginModelBase plugin = workspaceBundle("plugin.a", "1.0.0").findModel();
 
-		String entry = BundleLauncherHelper.writeBundleEntry(plugin, null, null);
+		String entry = BundleLauncherHelper.formatBundleEntry(plugin, null, null);
 		assertEquals("plugin.a*1.0.0", entry);
 	}
 
@@ -769,8 +772,8 @@
 
 		IPluginModelBase plugin = targetBundle("plugin.a", "2.0.0").findModel();
 
-		String entry = BundleLauncherHelper.writeBundleEntry(plugin, null, null);
-		assertEquals("plugin.a*2.0.0", entry);
+		String entry = BundleLauncherHelper.formatBundleEntry(plugin, null, null);
+		assertEquals("plugin.a", entry);
 	}
 
 	@Test
@@ -784,8 +787,8 @@
 
 		IPluginModelBase plugin = targetBundle("plugin.a", "2.0.0").findModel();
 
-		String entry = BundleLauncherHelper.writeBundleEntry(plugin, null, null);
-		assertEquals("plugin.a", entry);
+		String entry = BundleLauncherHelper.formatBundleEntry(plugin, null, null);
+		assertEquals("plugin.a*2.0.0", entry);
 	}
 
 	@Test
@@ -797,19 +800,15 @@
 
 		IPluginModelBase plugin = workspaceBundle("plugin.a", "1.0.0").findModel();
 
-		assertEquals("plugin.a*1.0.0", BundleLauncherHelper.writeBundleEntry(plugin, null, null));
-		assertEquals("plugin.a*1.0.0", BundleLauncherHelper.writeBundleEntry(plugin, "", ""));
-		assertEquals("plugin.a*1.0.0@4:true", BundleLauncherHelper.writeBundleEntry(plugin, "4", "true"));
-		assertEquals("plugin.a*1.0.0@4:", BundleLauncherHelper.writeBundleEntry(plugin, "4", ""));
-		assertEquals("plugin.a*1.0.0@:false", BundleLauncherHelper.writeBundleEntry(plugin, null, "false"));
+		assertEquals("plugin.a*1.0.0", BundleLauncherHelper.formatBundleEntry(plugin, null, null));
+		assertEquals("plugin.a*1.0.0", BundleLauncherHelper.formatBundleEntry(plugin, "", ""));
+		assertEquals("plugin.a*1.0.0@4:true", BundleLauncherHelper.formatBundleEntry(plugin, "4", "true"));
+		assertEquals("plugin.a*1.0.0@4:", BundleLauncherHelper.formatBundleEntry(plugin, "4", ""));
+		assertEquals("plugin.a*1.0.0@:false", BundleLauncherHelper.formatBundleEntry(plugin, null, "false"));
 	}
 
 	// --- utilities ---
 
-	private static NameVersionDescriptor bundle(String id, String version) {
-		return new NameVersionDescriptor(id, version);
-	}
-
 	private void assertGetMergedBundleMap(List<NameVersionDescriptor> workspacePlugins,
 			List<NameVersionDescriptor> targetPlugins, Consumer<ILaunchConfigurationWorkingCopy> launchConfigPreparer,
 			Set<BundleLocationDescriptor> expectedBundles) throws Exception {
@@ -835,11 +834,11 @@
 			expectedPluginMap.put(pd.findModel(), start);
 		});
 
-		assertEquals(expectedPluginMap, bundleMap);
+		assertPluginMapsEquals(null, expectedPluginMap, bundleMap);
 	}
 
 	private void setUpWorkspace(List<NameVersionDescriptor> workspacePlugins, List<NameVersionDescriptor> targetPlugins)
-			throws CoreException, IOException, InterruptedException {
+			throws Exception {
 		ProjectUtils.createWorkspacePluginProjects(workspacePlugins);
 		TargetPlatformUtil.setDummyBundlesAsTarget(targetPlugins, tpJarDirectory);
 	}
@@ -853,21 +852,4 @@
 		wc.setAttribute(IPDELauncherConstants.USE_DEFAULT, false);
 		return wc;
 	}
-
-	private static BundleLocationDescriptor workspaceBundle(String id, String version) {
-		Objects.requireNonNull(version);
-		return () -> findWorkspaceModel(id, version);
-	}
-
-	private static BundleLocationDescriptor targetBundle(String id, String version) {
-		Objects.requireNonNull(version);
-		// PluginRegistry.findModel does not consider external models when
-		// workspace models are present and returns the 'last' plug-in if
-		// multiple with the same version exist
-		return () -> findTargetModel(id, version);
-	}
-
-	private static interface BundleLocationDescriptor {
-		IPluginModelBase findModel();
-	}
 }
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/target/AbstractTargetTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/target/AbstractTargetTest.java
index 84475b0..153b367 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/target/AbstractTargetTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/target/AbstractTargetTest.java
@@ -34,6 +34,7 @@
 import org.eclipse.pde.core.plugin.TargetPlatform;
 import org.eclipse.pde.core.target.*;
 import org.eclipse.pde.internal.core.PDECore;
+import org.eclipse.pde.ui.tests.PDETestCase;
 import org.eclipse.pde.ui.tests.PDETestsPlugin;
 import org.eclipse.pde.ui.tests.util.TargetPlatformUtil;
 import org.osgi.framework.BundleContext;
@@ -43,8 +44,7 @@
 /**
  * Common utility methods for target definition tests
  */
-public abstract class AbstractTargetTest {
-
+public abstract class AbstractTargetTest extends PDETestCase {
 
 	/**
 	 * Returns the target platform service or <code>null</code> if none
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/target/WorkspaceTargetDefinitionTests.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/target/WorkspaceTargetDefinitionTests.java
index 81c82c8..cdda941 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/target/WorkspaceTargetDefinitionTests.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/target/WorkspaceTargetDefinitionTests.java
@@ -33,8 +33,10 @@
 
 	private static final String PROJECT_NAME = "WorkspaceTargetDefinitionTests";
 
+	@Override
 	@Before
 	public void setUp() throws Exception {
+		super.setUp();
 		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(PROJECT_NAME);
 		if (!project.exists()){
 			project.create(null);
@@ -44,6 +46,7 @@
 		assertTrue("Could not open test project", project.isOpen());
 	}
 
+	@Override
 	@After
 	public void tearDown() throws Exception {
 		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(PROJECT_NAME);
@@ -51,6 +54,7 @@
 			project.delete(true, null);
 		}
 		assertFalse("Could not delete test project",project.exists());
+		super.tearDown();
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/FreezeMonitor.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/FreezeMonitor.java
new file mode 100644
index 0000000..d4c40e5
--- /dev/null
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/FreezeMonitor.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *   Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+
+package org.eclipse.pde.ui.tests.util;
+
+import java.lang.management.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+
+public class FreezeMonitor {
+
+	public static final long FROZEN_TEST_TIMEOUT_MS = 60_000;
+
+	private static /* @Nullable */ Job monitorJob;
+
+	/**
+	 * Will dump JVM threads if test runs over one minute
+	 */
+	public static void expectCompletionInAMinute() {
+		expectCompletionIn(FROZEN_TEST_TIMEOUT_MS);
+	}
+
+	/**
+	 * Will dump JVM threads if test runs over given time
+	 */
+	public static void expectCompletionIn(final long timeout) {
+		done();
+		monitorJob = new Job("FreezeMonitor") {
+			@Override
+			public IStatus run(IProgressMonitor monitor) {
+				if (monitor.isCanceled()) {
+					return Status.CANCEL_STATUS;
+				}
+				StringBuilder result = new StringBuilder();
+				result.append("Possible frozen test case\n");
+				ThreadMXBean threadStuff = ManagementFactory.getThreadMXBean();
+				ThreadInfo[] allThreads = threadStuff.getThreadInfo(threadStuff.getAllThreadIds(), 200);
+				for (ThreadInfo threadInfo : allThreads) {
+					result.append("\"");
+					result.append(threadInfo.getThreadName());
+					result.append("\": ");
+					result.append(threadInfo.getThreadState());
+					result.append("\n");
+					final StackTraceElement[] elements = threadInfo.getStackTrace();
+					for (StackTraceElement element : elements) {
+						result.append("    ");
+						result.append(element);
+						result.append("\n");
+					}
+					result.append("\n");
+				}
+				System.out.println(result.toString());
+				return Status.OK_STATUS;
+			}
+
+			@Override
+			public boolean belongsTo(Object family) {
+				return FreezeMonitor.class == family;
+			}
+		};
+		monitorJob.schedule(timeout);
+	}
+
+	public static void done() {
+		if (monitorJob != null) {
+			monitorJob.cancel();
+			monitorJob = null;
+		}
+	}
+}
\ No newline at end of file
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/ProjectUtils.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/ProjectUtils.java
index 7ededbe..51d51e8 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/ProjectUtils.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/ProjectUtils.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2008, 2021 IBM Corporation and others.
+ * Copyright (c) 2008, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -18,12 +18,14 @@
 import java.net.URL;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.launching.environments.IExecutionEnvironment;
 import org.eclipse.pde.core.project.IBundleProjectDescription;
+import org.eclipse.pde.core.project.IBundleProjectService;
 import org.eclipse.pde.core.target.NameVersionDescriptor;
 import org.eclipse.pde.internal.ui.wizards.IProjectProvider;
 import org.eclipse.pde.internal.ui.wizards.plugin.NewProjectCreationOperation;
@@ -158,23 +160,33 @@
 	public static List<IProject> createWorkspacePluginProjects(List<NameVersionDescriptor> workspacePlugins)
 			throws CoreException {
 		List<IProject> projects = new ArrayList<>();
-		for (NameVersionDescriptor pluginDescription : workspacePlugins) {
-			String bundleSymbolicName = pluginDescription.getId();
-			String bundleVersion = pluginDescription.getVersion();
-			String projectName = bundleSymbolicName + bundleVersion.replace('.', '_');
-			projects.add(createPluginProject(projectName, bundleSymbolicName, bundleVersion));
+		for (NameVersionDescriptor plugin : workspacePlugins) {
+			projects.add(createPluginProject(plugin.getId(), plugin.getVersion()));
 		}
 		return projects;
 	}
 
+	public static IProject createPluginProject(String bundleSymbolicName, String bundleVersion) throws CoreException {
+		return createPluginProject(bundleSymbolicName + bundleVersion.replace('.', '_'), bundleSymbolicName,
+				bundleVersion);
+	}
+
 	public static IProject createPluginProject(String projectName, String bundleSymbolicName, String version)
 			throws CoreException {
+		return createPluginProject(projectName, bundleSymbolicName, version, (d, s) -> {
+		});
+	}
+
+	public static IProject createPluginProject(String projectName, String bundleSymbolicName, String version,
+			BiConsumer<IBundleProjectDescription, IBundleProjectService> setup) throws CoreException {
 		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
-		IBundleProjectDescription description = ProjectCreationTests.getBundleProjectService().getDescription(project);
+		IBundleProjectService bundleProjectService = ProjectCreationTests.getBundleProjectService();
+		IBundleProjectDescription description = bundleProjectService.getDescription(project);
 		description.setSymbolicName(bundleSymbolicName);
 		if (version != null) {
 			description.setBundleVersion(Version.parseVersion(version));
 		}
+		setup.accept(description, bundleProjectService);
 		description.apply(null);
 		return project;
 	}
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/TargetPlatformUtil.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/TargetPlatformUtil.java
index 759d37e..ed8218a 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/TargetPlatformUtil.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/TargetPlatformUtil.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2019, 2021 Julian Honnen and others.
+ *  Copyright (c) 2019, 2022 Julian Honnen and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -69,7 +69,7 @@
 
 		addRunningPlatformBundles(locations, included, bundleFilter);
 
-		ITargetLocation location = createDummyBundlesLocation(targetPlugins, jarDirectory);
+		ITargetLocation location = createDummyBundlesLocation(targetPlugins, Map.of(), jarDirectory);
 		locations.add(location);
 		included.addAll(targetPlugins);
 
@@ -129,21 +129,34 @@
 
 	public static void setDummyBundlesAsTarget(List<NameVersionDescriptor> targetPlugins, Path jarDirectory)
 			throws IOException, InterruptedException {
-		ITargetLocation location = createDummyBundlesLocation(targetPlugins, jarDirectory);
+		ITargetLocation location = createDummyBundlesLocation(targetPlugins, Map.of(), jarDirectory);
 		createAndSetTargetForWorkspace(null, List.of(location), targetPlugins);
 	}
 
-	private static ITargetLocation createDummyBundlesLocation(List<NameVersionDescriptor> targetPlugins,
-			Path jarDirectory) throws IOException {
+	public static void setDummyBundlesAsTarget(Map<NameVersionDescriptor, Map<String, String>> pluginDescriptions,
+			Path jarDirectory) throws IOException, InterruptedException {
+		Set<NameVersionDescriptor> pluginIds = pluginDescriptions.keySet();
+		ITargetLocation location = createDummyBundlesLocation(pluginIds, pluginDescriptions, jarDirectory);
+		createAndSetTargetForWorkspace(null, List.of(location), pluginIds);
+	}
+
+	private static ITargetLocation createDummyBundlesLocation(Collection<NameVersionDescriptor> targetPlugins,
+			Map<NameVersionDescriptor, Map<String, String>> pluginAttributes, Path jarDirectory) throws IOException {
+		Path pluginsDirectory = jarDirectory.resolve("plugins");
+		Files.createDirectories(pluginsDirectory);
 		for (NameVersionDescriptor bundleNameVersion : targetPlugins) {
 
 			Manifest manifest = createDummyBundleManifest(bundleNameVersion.getId(), bundleNameVersion.getVersion());
+			Map<String, String> extraAttributes = pluginAttributes.get(bundleNameVersion);
+			if (extraAttributes != null) {
+				extraAttributes.forEach(manifest.getMainAttributes()::putValue);
+			}
 
 			Attributes mainAttributes = manifest.getMainAttributes();
 			String bundleSymbolicName = Objects.requireNonNull(mainAttributes.getValue(Constants.BUNDLE_SYMBOLICNAME));
 			String bundleVersion = Objects.requireNonNull(mainAttributes.getValue(Constants.BUNDLE_VERSION));
 
-			Path jarPath = jarDirectory.resolve(bundleSymbolicName + "_" + bundleVersion + ".jar");
+			Path jarPath = pluginsDirectory.resolve(bundleSymbolicName + "_" + bundleVersion + ".jar");
 			try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(jarPath));) {
 				out.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME));
 				manifest.write(out);
diff --git a/ui/org.eclipse.pde.ui.tests/tests/build.properties/build.properties.tests.zip b/ui/org.eclipse.pde.ui.tests/tests/build.properties/build.properties.tests.zip
index c156667..dc70431 100644
--- a/ui/org.eclipse.pde.ui.tests/tests/build.properties/build.properties.tests.zip
+++ b/ui/org.eclipse.pde.ui.tests/tests/build.properties/build.properties.tests.zip
Binary files differ
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/.classpath b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/.classpath
deleted file mode 100644
index e801ebf..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/.classpath
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
-	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/.project b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/.project
deleted file mode 100644
index cd9fd47..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/.project
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>bundle-Fragment</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.ManifestBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.SchemaBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.pde.PluginNature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/META-INF/MANIFEST.MF
deleted file mode 100644
index 176b574..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,8 +0,0 @@
-Manifest-Version: 1.0
-Bundle-ManifestVersion: 2
-Bundle-Name: Bundle with Import-Package
-Bundle-SymbolicName: bundle.fragment
-Bundle-Version: 1.0.0.qualifier
-Automatic-Module-Name: bundle.fragment
-Bundle-RequiredExecutionEnvironment: JavaSE-11
-Fragment-Host: bundle.a
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/build.properties b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/build.properties
deleted file mode 100644
index 34d2e4d..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-Fragment/build.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/.classpath b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/.classpath
deleted file mode 100644
index e801ebf..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/.classpath
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
-	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/.project b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/.project
deleted file mode 100644
index 5e4511b..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/.project
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>bundle-ImportPackage</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.ManifestBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.SchemaBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.pde.PluginNature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/META-INF/MANIFEST.MF
deleted file mode 100644
index 301b268..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,8 +0,0 @@
-Manifest-Version: 1.0
-Bundle-ManifestVersion: 2
-Bundle-Name: Bundle with Import-Package
-Bundle-SymbolicName: bundle.importPackage
-Bundle-Version: 1.0.0.qualifier
-Automatic-Module-Name: bundle.importPackage
-Bundle-RequiredExecutionEnvironment: JavaSE-11
-Import-Package: bundle.a.pack
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/build.properties b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/build.properties
deleted file mode 100644
index 34d2e4d..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-ImportPackage/build.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/.classpath b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/.classpath
deleted file mode 100644
index e801ebf..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/.classpath
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
-	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/.project b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/.project
deleted file mode 100644
index 0f6ff89..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/.project
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>bundle-RequireBundle</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.ManifestBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.SchemaBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.pde.PluginNature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/META-INF/MANIFEST.MF
deleted file mode 100644
index 302b93f..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,8 +0,0 @@
-Manifest-Version: 1.0
-Bundle-ManifestVersion: 2
-Bundle-Name: Bundle with Import-Package
-Bundle-SymbolicName: bundle.requireBundle
-Bundle-Version: 1.0.0.qualifier
-Automatic-Module-Name: bundle.requireBundle
-Bundle-RequiredExecutionEnvironment: JavaSE-11
-Require-Bundle: bundle.a;bundle-version="[1.0.0,2.0.0)"
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/build.properties b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/build.properties
deleted file mode 100644
index 34d2e4d..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle-RequireBundle/build.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/.classpath b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/.classpath
deleted file mode 100644
index e801ebf..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/.classpath
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
-	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/.project b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/.project
deleted file mode 100644
index 0561798..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/.project
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>bundle.A-1.0.0</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.ManifestBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.SchemaBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.pde.PluginNature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/META-INF/MANIFEST.MF
deleted file mode 100644
index 23d4ef5..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,8 +0,0 @@
-Manifest-Version: 1.0
-Bundle-ManifestVersion: 2
-Bundle-Name: Bundle A
-Bundle-SymbolicName: bundle.a
-Bundle-Version: 1.0.0.qualifier
-Automatic-Module-Name: bundle.a
-Bundle-RequiredExecutionEnvironment: JavaSE-11
-Export-Package: bundle.a.pack
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/build.properties b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/build.properties
deleted file mode 100644
index 34d2e4d..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/build.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/src/bundle/a/pack/AClass.java b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/src/bundle/a/pack/AClass.java
deleted file mode 100644
index 4978ad5..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-1.0.0/src/bundle/a/pack/AClass.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package bundle.a.pack;
-
-public class Test {
-
-}
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/.classpath b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/.classpath
deleted file mode 100644
index e801ebf..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/.classpath
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
-	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/.project b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/.project
deleted file mode 100644
index bfba4da..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/.project
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>bundle.A-2.0.0</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.ManifestBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.pde.SchemaBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>org.eclipse.pde.PluginNature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/META-INF/MANIFEST.MF
deleted file mode 100644
index 1a4584e..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,8 +0,0 @@
-Manifest-Version: 1.0
-Bundle-ManifestVersion: 2
-Bundle-Name: Bundle A
-Bundle-SymbolicName: bundle.a
-Bundle-Version: 2.0.0.qualifier
-Automatic-Module-Name: bundle.a
-Bundle-RequiredExecutionEnvironment: JavaSE-11
-Export-Package: bundle.a.pack
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/build.properties b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/build.properties
deleted file mode 100644
index 34d2e4d..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/build.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/src/bundle/a/pack/AClass.java b/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/src/bundle/a/pack/AClass.java
deleted file mode 100644
index 4978ad5..0000000
--- a/ui/org.eclipse.pde.ui.tests/tests/projects/requirements/bundle.A-2.0.0/src/bundle/a/pack/AClass.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package bundle.a.pack;
-
-public class Test {
-
-}
diff --git a/ui/org.eclipse.pde.ui.tests/tests/targets/target-files/SoftwareSiteTarget.trgt b/ui/org.eclipse.pde.ui.tests/tests/targets/target-files/SoftwareSiteTarget.trgt
index 39a8ecc..8fa7462 100644
--- a/ui/org.eclipse.pde.ui.tests/tests/targets/target-files/SoftwareSiteTarget.trgt
+++ b/ui/org.eclipse.pde.ui.tests/tests/targets/target-files/SoftwareSiteTarget.trgt
@@ -3,7 +3,7 @@
 <location includeAllPlatforms="false" includeMode="slicer" includeSource="true" type="InstallableUnit">
 <unit id="org.eclipse.egit.feature.group" version="0.11.3"/>
 <unit id="org.eclipse.jgit.feature.group" version="0.11.3"/>
-<repository location="http://download.eclipse.org/releases/indigo"/>
+<repository location="https://download.eclipse.org/releases/indigo"/>
 </location>
 </locations>
 </target>
\ No newline at end of file
diff --git a/ui/org.eclipse.pde.ui/pom.xml b/ui/org.eclipse.pde.ui/pom.xml
index 977a28d..151d4a4 100644
--- a/ui/org.eclipse.pde.ui/pom.xml
+++ b/ui/org.eclipse.pde.ui/pom.xml
@@ -13,11 +13,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.ui</artifactId>
   <version>3.13.400-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPlugin.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPlugin.java
index f502caf..7ff0156 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPlugin.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPlugin.java
@@ -142,7 +142,7 @@
 	 * @param message message to add to the error log
 	 */
 	public static void log(String message) {
-		log(new Status(IStatus.ERROR, getPluginId(), message));
+		log(Status.error(message));
 	}
 
 	public static void logException(Throwable e, final String title, String message) {
@@ -164,7 +164,7 @@
 			if (message == null) {
 				message = e.toString();
 			}
-			status = new Status(IStatus.ERROR, getPluginId(), IStatus.OK, message, e);
+			status = Status.error(message, e);
 		}
 		getDefault().getLog().log(status);
 		Display display = SWTUtil.getStandardDisplay();
@@ -189,7 +189,7 @@
 			}
 		}
 		if (status == null) {
-			status = new Status(IStatus.ERROR, getPluginId(), IStatus.OK, e.getMessage(), e);
+			status = Status.error(e.getMessage(), e);
 		}
 		log(status);
 	}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java
index cf95eba..2a79503 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEUIMessages.java
@@ -561,6 +561,7 @@
 	public static String MainPreferencePage_WorkspacePluginsOverrideTarget;
 	public static String MainPreferencePage_WorkspacePluginsOverrideTargetTooltip;
 	public static String MainPreferencePage_DisableAPIAnalysisBuilder;
+	public static String MainPreferencePage_RunAPIAnalysisBuilderAsJob;
 
 	public static String MainPreferencePage_test_plugin_pattern_group;
 	public static String MainPreferencePage_test_plugin_pattern_label;
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/build/GeneratePluginBuildFileAction.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/build/GeneratePluginBuildFileAction.java
index d32e1b4..6647846 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/build/GeneratePluginBuildFileAction.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/build/GeneratePluginBuildFileAction.java
@@ -43,8 +43,7 @@
 		BuildErrorReporter buildErrorReporter = new BuildErrorReporter(fManifestFile);
 		IResource buildXML = project.findMember("build.xml"); //$NON-NLS-1$
 		if (buildXML != null && buildXML.exists() == true && buildErrorReporter.isCustomBuild() == true) {
-			IStatus warnFail = new Status(IStatus.WARNING, model.getPluginBase().getId(), PDEUIMessages.BuildPluginAction_WarningCustomBuildExists);
-			throw new CoreException(warnFail);
+			throw new CoreException(Status.warning(PDEUIMessages.BuildPluginAction_WarningCustomBuildExists));
 		}
 		BuildScriptGenerator generator = new BuildScriptGenerator();
 		AbstractScriptGenerator.setEmbeddedSource(AbstractScriptGenerator.getDefaultEmbeddedSource());
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/build/RuntimeInstallJob.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/build/RuntimeInstallJob.java
index 4b92209..b612327 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/build/RuntimeInstallJob.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/build/RuntimeInstallJob.java
@@ -37,7 +37,6 @@
 import org.eclipse.pde.internal.build.site.QualifierReplacer;
 import org.eclipse.pde.internal.core.exports.FeatureExportInfo;
 import org.eclipse.pde.internal.core.ifeature.IFeatureModel;
-import org.eclipse.pde.internal.ui.PDEPlugin;
 import org.eclipse.pde.internal.ui.PDEUIMessages;
 
 /**
@@ -95,17 +94,16 @@
 
 			IProfileRegistry profileRegistry = (IProfileRegistry) session.getProvisioningAgent().getService(IProfileRegistry.SERVICE_NAME);
 			if (profileRegistry == null) {
-				return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), PDEUIMessages.RuntimeInstallJob_ErrorCouldntOpenProfile);
+				return Status.error(PDEUIMessages.RuntimeInstallJob_ErrorCouldntOpenProfile);
 			}
 			IProfile profile = profileRegistry.getProfile(IProfileRegistry.SELF);
 			if (profile == null) {
-				return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), PDEUIMessages.RuntimeInstallJob_ErrorCouldntOpenProfile);
+				return Status.error(PDEUIMessages.RuntimeInstallJob_ErrorCouldntOpenProfile);
 			}
 
 			List<IInstallableUnit> toInstall = new ArrayList<>();
 			for (Object item : fInfo.items) {
-				subMonitor.subTask(
-						NLS.bind(PDEUIMessages.RuntimeInstallJob_Creating_installable_unit, item.toString()));
+				subMonitor.subTask(NLS.bind(PDEUIMessages.RuntimeInstallJob_Creating_installable_unit, item));
 
 				//Get the installable unit from the repo
 				String id = null;
@@ -119,7 +117,7 @@
 				}
 
 				if (id == null && version == null) {
-					return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), NLS.bind(PDEUIMessages.RuntimeInstallJob_ErrorCouldNotGetIdOrVersion, item.toString()));
+					return Status.error(NLS.bind(PDEUIMessages.RuntimeInstallJob_ErrorCouldNotGetIdOrVersion, item));
 				}
 
 				// Use the same qualifier replacement as the export operation used
@@ -129,7 +127,7 @@
 				Version newVersion = Version.parseVersion(version);
 				IQueryResult<?> queryMatches = metaRepo.query(QueryUtil.createIUQuery(id, newVersion), monitor);
 				if (queryMatches.isEmpty()) {
-					return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), NLS.bind(PDEUIMessages.RuntimeInstallJob_ErrorCouldNotFindUnitInRepo, new String[] {id, version}));
+					return Status.error(NLS.bind(PDEUIMessages.RuntimeInstallJob_ErrorCouldNotFindUnitInRepo, id, version));
 				}
 
 				IInstallableUnit iuToInstall = (IInstallableUnit) queryMatches.iterator().next();
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/compare/ManifestStructureCreator.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/compare/ManifestStructureCreator.java
index 8a5cd83..566bf06 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/compare/ManifestStructureCreator.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/compare/ManifestStructureCreator.java
@@ -21,7 +21,6 @@
 import org.eclipse.core.runtime.*;
 import org.eclipse.jface.text.*;
 import org.eclipse.jface.text.rules.FastPartitioner;
-import org.eclipse.pde.internal.ui.PDEPlugin;
 import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.pde.internal.ui.editor.text.ManifestPartitionScanner;
 import org.eclipse.swt.graphics.Image;
@@ -218,7 +217,7 @@
 		} catch (IOException ex) {
 			if (adapter != null)
 				adapter.disconnect(input);
-			throw new CoreException(new Status(IStatus.ERROR, PDEPlugin.getPluginId(), 0, PDEUIMessages.ManifestStructureCreator_errorMessage, ex));
+			throw new CoreException(Status.error(PDEUIMessages.ManifestStructureCreator_errorMessage, ex));
 		}
 
 		return rootNode;
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/FeatureSelectionDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/FeatureSelectionDialog.java
index b2a07ae..677faa7 100755
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/FeatureSelectionDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/FeatureSelectionDialog.java
@@ -179,7 +179,7 @@
 
 	@Override
 	protected IStatus validateItem(Object item) {
-		return new Status(IStatus.OK, IPDEUIConstants.PLUGIN_ID, 0, "", null); //$NON-NLS-1$
+		return Status.OK_STATUS;
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/PluginSelectionDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/PluginSelectionDialog.java
index 7116747..bd4e76c 100755
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/PluginSelectionDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/PluginSelectionDialog.java
@@ -272,7 +272,7 @@
 
 	@Override
 	protected IStatus validateItem(Object item) {
-		return new Status(IStatus.OK, IPDEUIConstants.PLUGIN_ID, 0, "", null); //$NON-NLS-1$
+		return Status.OK_STATUS;
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/RepositoryDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/RepositoryDialog.java
index 0c059b8..7955550 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/RepositoryDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/dialogs/RepositoryDialog.java
@@ -23,7 +23,6 @@
 import org.eclipse.jface.dialogs.StatusDialog;
 import org.eclipse.jface.widgets.WidgetFactory;
 import org.eclipse.jface.window.Window;
-import org.eclipse.pde.internal.ui.IPDEUIConstants;
 import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.dnd.*;
@@ -77,25 +76,24 @@
 		updateStatus(isValidURL(fLocationStr));
 	}
 
-	private Status isValidURL(String location) {
+	private IStatus isValidURL(String location) {
 		if (location.length() == 0) {
-			return new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, PDEUIMessages.UpdatesSection_ErrorInvalidURL);
+			return Status.error(PDEUIMessages.UpdatesSection_ErrorInvalidURL);
 		}
 		if (!(location.startsWith("http://") //$NON-NLS-1$
 				|| location.startsWith("https://") //$NON-NLS-1$
 				|| location.startsWith("file:/"))) { //$NON-NLS-1$
-			return new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, PDEUIMessages.UpdatesSection_ErrorInvalidURL);
+			return Status.error(PDEUIMessages.UpdatesSection_ErrorInvalidURL);
 		}
 		try {
 			URL url = new URL(location);
 			if (url.getHost().trim().isBlank() && url.getPath().isBlank()) {
-				return new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID,
-						PDEUIMessages.UpdatesSection_ErrorInvalidURL);
+				return Status.error(PDEUIMessages.UpdatesSection_ErrorInvalidURL);
 			}
 		} catch (MalformedURLException e) {
-			return new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, PDEUIMessages.UpdatesSection_ErrorInvalidURL);
+			return Status.error(PDEUIMessages.UpdatesSection_ErrorInvalidURL);
 		}
-		return new Status(IStatus.OK, IPDEUIConstants.PLUGIN_ID, ""); //$NON-NLS-1$
+		return Status.OK_STATUS;
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/JarEntryFile.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/JarEntryFile.java
index 6d4fab8..e613866 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/JarEntryFile.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/JarEntryFile.java
@@ -19,7 +19,6 @@
 import java.util.zip.ZipFile;
 import org.eclipse.core.resources.IStorage;
 import org.eclipse.core.runtime.*;
-import org.eclipse.pde.internal.ui.IPDEUIConstants;
 
 public class JarEntryFile extends PlatformObject implements IStorage {
 
@@ -37,7 +36,7 @@
 			ZipEntry zipEntry = fZipFile.getEntry(fEntryName);
 			return fZipFile.getInputStream(zipEntry);
 		} catch (Exception e) {
-			throw new CoreException(new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, IStatus.ERROR, e.getMessage(), e));
+			throw new CoreException(Status.error(e.getMessage(), e));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/AddLibraryDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/AddLibraryDialog.java
index 987504b..c68c605 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/AddLibraryDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/AddLibraryDialog.java
@@ -42,22 +42,22 @@
 	class DuplicateStatusValidator {
 		public IStatus validate(String text) {
 			if (text.length() == 0)
-				return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, PDEUIMessages.AddLibraryDialog_emptyLibraries, null);
+				return Status.error(PDEUIMessages.AddLibraryDialog_emptyLibraries);
 
 			if (text.indexOf(' ') != -1)
-				return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, PDEUIMessages.AddLibraryDialog_nospaces, null);
+				return Status.error(PDEUIMessages.AddLibraryDialog_nospaces);
 
 			if (libraries == null || libraries.length == 0)
-				return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", null); //$NON-NLS-1$
+				return Status.OK_STATUS;
 
 			if (!text.endsWith(".jar") && !text.endsWith("/") && !text.equals(".")) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 				text += "/"; //$NON-NLS-1$
 
 			for (String library : libraries) {
 				if (library.equals(text))
-					return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, PDEUIMessages.BuildEditor_RuntimeInfoSection_duplicateLibrary, null);
+					return Status.error(PDEUIMessages.BuildEditor_RuntimeInfoSection_duplicateLibrary);
 			}
-			return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", null); //$NON-NLS-1$
+			return Status.OK_STATUS;
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/BuildClasspathSection.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/BuildClasspathSection.java
index 6f4f1ac..5bf2bde 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/BuildClasspathSection.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/BuildClasspathSection.java
@@ -75,11 +75,9 @@
 		@Override
 		public IStatus validate(Object[] elements) {
 			if (isValid(elements)) {
-				return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", //$NON-NLS-1$
-						null);
+				return Status.OK_STATUS;
 			}
-			return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, "", //$NON-NLS-1$
-					null);
+			return Status.error(""); //$NON-NLS-1$
 		}
 
 		private boolean isOfAcceptedType(Object o) {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/RuntimeInfoSection.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/RuntimeInfoSection.java
index 53f4d64..47ddd67 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/RuntimeInfoSection.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/build/RuntimeInfoSection.java
@@ -789,15 +789,14 @@
 		dialog.setMessage(message);
 
 		dialog.setValidator(selection -> {
-			String id = PDEPlugin.getPluginId();
 			if (selection == null || selection.length != 1 || !(selection[0] instanceof IFolder))
-				return new Status(IStatus.ERROR, id, IStatus.ERROR, "", null); //$NON-NLS-1$
+				return Status.error(""); //$NON-NLS-1$
 
 			String folderPath = ((IFolder) selection[0]).getProjectRelativePath().addTrailingSeparator().toString();
-			if (entry != null && entry.contains(folderPath))
-				return new Status(IStatus.ERROR, id, IStatus.ERROR, PDEUIMessages.BuildEditor_RuntimeInfoSection_duplicateFolder, null);
-
-			return new Status(IStatus.OK, id, IStatus.OK, "", null); //$NON-NLS-1$
+			if (entry != null && entry.contains(folderPath)) {
+				return Status.error(PDEUIMessages.BuildEditor_RuntimeInfoSection_duplicateFolder);
+			}
+			return Status.OK_STATUS;
 		});
 
 		if (dialog.open() == Window.OK)
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/category/CategoryLabelProvider.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/category/CategoryLabelProvider.java
index a5eea1c..f6aaa97 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/category/CategoryLabelProvider.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/category/CategoryLabelProvider.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2016 EclipseSource and others.
+ * Copyright (c) 2013, 2021 EclipseSource and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -74,7 +74,7 @@
 	@Override
 	public String getText(Object element) {
 		if (element instanceof ISiteCategoryDefinition)
-			return ((ISiteCategoryDefinition) element).getName();
+			return ((ISiteCategoryDefinition) element).getLabel();
 		if (element instanceof SiteCategoryDefinitionAdapter) {
 			return getText(((SiteCategoryDefinitionAdapter) element).category);
 		}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/context/InputContext.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/context/InputContext.java
index 7932de2..570f63c 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/context/InputContext.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/context/InputContext.java
@@ -177,9 +177,9 @@
 					Shell shell = fEditor.getEditorSite().getShell();
 					IStatus validateStatus = PDEPlugin.getWorkspace().validateEdit(new IFile[] {file}, shell);
 					fValidated = true; // to prevent loops
-					if (validateStatus.getSeverity() != IStatus.OK)
+					if (!validateStatus.isOK())
 						ErrorDialog.openError(shell, fEditor.getTitle(), null, validateStatus);
-					return validateStatus.getSeverity() == IStatus.OK;
+					return validateStatus.isOK();
 				}
 			}
 		}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/RequiresSection.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/RequiresSection.java
index 4a1bb47..8d0ae45 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/RequiresSection.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/feature/RequiresSection.java
@@ -346,8 +346,7 @@
 	}
 
 	private void logNullFeatureImport(Object obj) {
-		PDEPlugin.log(new Status(IStatus.WARNING, PDEPlugin.getPluginId(),
-				NLS.bind(PDEUIMessages.RequiresSection_nullLog, obj)));
+		PDEPlugin.log(Status.warning(NLS.bind(PDEUIMessages.RequiresSection_nullLog, obj)));
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/BlankQuery.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/BlankQuery.java
index 2db7e5e..b669568 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/BlankQuery.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/BlankQuery.java
@@ -15,7 +15,6 @@
 
 import org.eclipse.core.runtime.*;
 import org.eclipse.pde.internal.core.text.bundle.PackageObject;
-import org.eclipse.pde.internal.ui.IPDEUIConstants;
 import org.eclipse.pde.internal.ui.search.SearchResult;
 import org.eclipse.search.ui.ISearchQuery;
 import org.eclipse.search.ui.ISearchResult;
@@ -31,7 +30,7 @@
 	@Override
 	public IStatus run(IProgressMonitor monitor) throws OperationCanceledException {
 		monitor.done();
-		return new Status(IStatus.OK, IPDEUIConstants.PLUGIN_ID, IStatus.OK, "", null); //$NON-NLS-1$
+		return Status.OK_STATUS;
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ExtensionPointDetails.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ExtensionPointDetails.java
index be7edfc..c08eadd 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ExtensionPointDetails.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/ExtensionPointDetails.java
@@ -184,17 +184,13 @@
 					}
 				});
 				dialog.setValidator(selection -> {
-					IPluginModelBase model = (IPluginModelBase) getPage().getPDEEditor().getAggregateModel();
-					String pluginName = model.getPluginBase().getId();
-
 					if (selection == null || selection.length != 1 || !(selection[0] instanceof IFile))
-						return new Status(IStatus.ERROR, pluginName, IStatus.ERROR, PDEUIMessages.ManifestEditor_ExtensionPointDetails_validate_errorStatus, null);
+						return Status.error(PDEUIMessages.ManifestEditor_ExtensionPointDetails_validate_errorStatus);
 					IFile file = (IFile) selection[0];
 					String ext = file.getFullPath().getFileExtension();
 					if ("exsd".equals(ext) || "mxsd".equals(ext)) //$NON-NLS-1$ //$NON-NLS-2$
-						return new Status(IStatus.OK, pluginName, IStatus.OK, "", null); //$NON-NLS-1$
-					return new Status(IStatus.ERROR, pluginName, IStatus.ERROR,
-							PDEUIMessages.ManifestEditor_ExtensionPointDetails_validate_errorStatus, null);
+						return Status.OK_STATUS;
+					return Status.error(PDEUIMessages.ManifestEditor_ExtensionPointDetails_validate_errorStatus);
 				});
 				dialog.setDoubleClickSelects(true);
 				dialog.setStatusLineAboveButtons(true);
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/JarSelectionValidator.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/JarSelectionValidator.java
index 8c17dc7..8f98965 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/JarSelectionValidator.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/JarSelectionValidator.java
@@ -14,7 +14,6 @@
 package org.eclipse.pde.internal.ui.editor.plugin;
 
 import org.eclipse.core.runtime.*;
-import org.eclipse.pde.internal.ui.PDEPlugin;
 import org.eclipse.ui.dialogs.ISelectionStatusValidator;
 
 /**
@@ -41,11 +40,9 @@
 	@Override
 	public IStatus validate(Object[] elements) {
 		if (isValidSelection(elements)) {
-			return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", //$NON-NLS-1$
-					null);
+			return Status.OK_STATUS;
 		}
-		return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, "", //$NON-NLS-1$
-				null);
+		return Status.error("", null); //$NON-NLS-1$
 	}
 
 	private boolean isValidSelection(Object[] selection) {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/NewRuntimeLibraryDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/NewRuntimeLibraryDialog.java
index ff3353c..e29eed3 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/NewRuntimeLibraryDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/NewRuntimeLibraryDialog.java
@@ -13,13 +13,12 @@
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.editor.plugin;
 
-import org.eclipse.core.runtime.Path;
-
 import java.util.HashSet;
 import org.eclipse.core.runtime.*;
 import org.eclipse.pde.core.plugin.IPluginLibrary;
 import org.eclipse.pde.internal.core.ClasspathUtilCore;
-import org.eclipse.pde.internal.ui.*;
+import org.eclipse.pde.internal.ui.IHelpContextIds;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
@@ -36,19 +35,18 @@
 
 	class DuplicateStatusValidator {
 		public IStatus validate(String text) {
-			String id = PDEPlugin.getPluginId();
 			if (text.length() == 0)
-				return new Status(IStatus.ERROR, id, IStatus.ERROR, PDEUIMessages.AddLibraryDialog_emptyLibraries, null);
+				return Status.error(PDEUIMessages.AddLibraryDialog_emptyLibraries);
 
 			if (text.indexOf(' ') != -1)
-				return new Status(IStatus.ERROR, id, IStatus.ERROR, PDEUIMessages.AddLibraryDialog_nospaces, null);
+				return Status.error(PDEUIMessages.AddLibraryDialog_nospaces);
 
 			if (libraries == null || libraries.length == 0)
-				return new Status(IStatus.OK, id, IStatus.OK, "", null); //$NON-NLS-1$
+				return Status.OK_STATUS;
 
 			if (librarySet.contains(new Path(ClasspathUtilCore.expandLibraryName(text))))
-				return new Status(IStatus.ERROR, id, IStatus.ERROR, PDEUIMessages.ManifestEditor_RuntimeLibraryDialog_validationError, null);
-			return new Status(IStatus.OK, id, IStatus.OK, "", null); //$NON-NLS-1$
+				return Status.error(PDEUIMessages.ManifestEditor_RuntimeLibraryDialog_validationError);
+			return Status.OK_STATUS;
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/rows/ResourceAttributeRow.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/rows/ResourceAttributeRow.java
index 82e21af..39f4a9c 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/rows/ResourceAttributeRow.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/plugin/rows/ResourceAttributeRow.java
@@ -20,7 +20,8 @@
 package org.eclipse.pde.internal.ui.editor.plugin.rows;
 
 import org.eclipse.core.resources.*;
-import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.ui.actions.ShowInNavigatorViewAction;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.jface.viewers.ViewerFilter;
@@ -128,10 +129,10 @@
 		dialog.setTitle(PDEUIMessages.ResourceAttributeCellEditor_title);
 		dialog.setMessage(PDEUIMessages.ResourceAttributeCellEditor_message);
 		dialog.setValidator(selection -> {
-			if (selection != null && selection.length > 0 && (selection[0] instanceof IFile || selection[0] instanceof IContainer))
-				return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", null); //$NON-NLS-1$
-
-			return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, "", null); //$NON-NLS-1$
+			if (selection != null && selection.length > 0 && (selection[0] instanceof IFile || selection[0] instanceof IContainer)) {
+				return Status.OK_STATUS;
+			}
+			return Status.error(""); //$NON-NLS-1$
 		});
 		if (dialog.open() == Window.OK) {
 			IResource res = (IResource) dialog.getFirstResult();
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PluginSection.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PluginSection.java
index acf3fbe..8acbcca 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PluginSection.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PluginSection.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2021 IBM Corporation and others.
+ * Copyright (c) 2005, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -35,6 +35,7 @@
 import org.eclipse.pde.core.IModelChangedEvent;
 import org.eclipse.pde.core.plugin.*;
 import org.eclipse.pde.internal.core.*;
+import org.eclipse.pde.internal.core.DependencyManager.Options;
 import org.eclipse.pde.internal.core.iproduct.*;
 import org.eclipse.pde.internal.core.iproduct.IProduct;
 import org.eclipse.pde.internal.core.util.VersionUtil;
@@ -354,7 +355,10 @@
 			return PluginRegistry.findModel(plugin.getId(), version, IMatchRules.PERFECT, null);
 		}).filter(Objects::nonNull).map(IPluginModelBase::getBundleDescription).collect(Collectors.toList());
 
-		Set<BundleDescription> dependencies = DependencyManager.findRequirementsClosure(list, includeOptional);
+		DependencyManager.Options[] options = includeOptional
+				? new Options[] { Options.INCLUDE_NON_TEST_FRAGMENTS, Options.INCLUDE_OPTIONAL_DEPENDENCIES }
+				: new Options[] { Options.INCLUDE_NON_TEST_FRAGMENTS };
+		Set<BundleDescription> dependencies = DependencyManager.findRequirementsClosure(list, options);
 
 		IProduct product = plugins[0].getProduct();
 		IProductModelFactory factory = product.getModel().getFactory();
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/ProductEditorContributor.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/ProductEditorContributor.java
index 6be8b26..401888f 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/ProductEditorContributor.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/ProductEditorContributor.java
@@ -13,9 +13,9 @@
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.editor.product;
 
-import org.eclipse.pde.internal.ui.editor.PDEFormEditorContributor;
+import org.eclipse.pde.internal.ui.editor.PDEFormTextEditorContributor;
 
-public class ProductEditorContributor extends PDEFormEditorContributor {
+public class ProductEditorContributor extends PDEFormTextEditorContributor {
 
 	public ProductEditorContributor() {
 		super("Target"); //$NON-NLS-1$
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/ProductValidateAction.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/ProductValidateAction.java
index 07b58bb..b4c3352 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/ProductValidateAction.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/ProductValidateAction.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2009, 2015 EclipseSource Corporation and others.
+ * Copyright (c) 2009, 2022 EclipseSource Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -59,8 +59,7 @@
 			}
 		}
 		try {
-			IPluginModelBase[] models = launchPlugins.toArray(new IPluginModelBase[launchPlugins.size()]);
-			LaunchValidationOperation operation = new ProductValidationOperation(models);
+			LaunchValidationOperation operation = new ProductValidationOperation(launchPlugins);
 			LaunchPluginValidator.runValidationOperation(operation, new NullProgressMonitor());
 			if (!operation.hasErrors()) {
 				MessageDialog.open(SWT.ICON_INFORMATION, PDEPlugin.getActiveWorkbenchShell(),
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PropertiesSection.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PropertiesSection.java
index 8650073..9e5f084 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PropertiesSection.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PropertiesSection.java
@@ -15,14 +15,14 @@
 package org.eclipse.pde.internal.ui.editor.product;
 
 import java.util.*;
-import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.dialogs.StatusDialog;
 import org.eclipse.jface.viewers.*;
 import org.eclipse.jface.window.Window;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.pde.core.IModelChangedEvent;
 import org.eclipse.pde.internal.core.iproduct.*;
-import org.eclipse.pde.internal.core.iproduct.IProduct;
 import org.eclipse.pde.internal.core.util.PDESchemaHelper;
 import org.eclipse.pde.internal.ui.*;
 import org.eclipse.pde.internal.ui.editor.PDEFormPage;
@@ -176,7 +176,7 @@
 			}
 
 			// Disable ok button on startup
-			updateStatus(new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, "")); //$NON-NLS-1$
+			updateStatus(Status.error("")); //$NON-NLS-1$
 
 			return comp;
 		}
@@ -184,12 +184,12 @@
 		protected void validate() {
 			String name = fName.getText().trim();
 			if (name.length() == 0) {
-				updateStatus(new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, PDEUIMessages.PropertiesSection_ErrorPropertyNoName));
+				updateStatus(Status.error(PDEUIMessages.PropertiesSection_ErrorPropertyNoName));
 			} else if (PDESchemaHelper.containsMatchingProperty(fExistingProperties, name,
 					fOS.getSelectionIndex() == 0 ? PDESchemaHelper.ALL_OS : COMBO_OSLABELS[fOS.getSelectionIndex()],
 					fArch.getSelectionIndex() == 0 ? PDESchemaHelper.ALL_ARCH
 							: COMBO_ARCHLABELS[fArch.getSelectionIndex()])) {
-				updateStatus(new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, NLS.bind(PDEUIMessages.PropertiesSection_ErrorPropertyExists, name)));
+				updateStatus(Status.error(NLS.bind(PDEUIMessages.PropertiesSection_ErrorPropertyExists, name)));
 			} else {
 				updateStatus(Status.OK_STATUS);
 			}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/schema/FilteredSchemaAttributeSelectionDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/schema/FilteredSchemaAttributeSelectionDialog.java
index b8d36ba..627fff2 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/schema/FilteredSchemaAttributeSelectionDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/schema/FilteredSchemaAttributeSelectionDialog.java
@@ -251,7 +251,7 @@
 
 	@Override
 	protected IStatus validateItem(Object item) {
-		return new Status(IStatus.OK, "org.eclipse.pde.ui", 0, "", null); //$NON-NLS-1$ //$NON-NLS-2$
+		return Status.OK_STATUS;
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/site/NewArchiveDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/site/NewArchiveDialog.java
index 55fe4f8..1d30ae2 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/site/NewArchiveDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/site/NewArchiveDialog.java
@@ -30,8 +30,6 @@
 
 	private IStatus fErrorStatus;
 
-	private IStatus fOkStatus;
-
 	private Text fPathText;
 
 	private ISiteArchive fSiteArchive;
@@ -90,20 +88,16 @@
 		}
 	}
 
-	private IStatus createErrorStatus(String message) {
-		return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.OK, message, null);
-	}
-
 	private void dialogChanged() {
 		IStatus status = null;
 		if (fUrlText.getText().length() == 0 || fPathText.getText().length() == 0)
 			status = getEmptyErrorStatus();
 		else {
 			if (hasPath(fPathText.getText()))
-				status = createErrorStatus(PDEUIMessages.NewArchiveDialog_alreadyExists);
+				status = Status.error(PDEUIMessages.NewArchiveDialog_alreadyExists);
 		}
 		if (status == null)
-			status = getOKStatus();
+			status = Status.OK_STATUS;
 		updateStatus(status);
 	}
 
@@ -124,17 +118,10 @@
 
 	private IStatus getEmptyErrorStatus() {
 		if (fErrorStatus == null)
-			fErrorStatus = createErrorStatus(PDEUIMessages.SiteEditor_NewArchiveDialog_error);
+			fErrorStatus = Status.error(PDEUIMessages.SiteEditor_NewArchiveDialog_error);
 		return fErrorStatus;
 	}
 
-	private IStatus getOKStatus() {
-		if (fOkStatus == null)
-			fOkStatus = new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", //$NON-NLS-1$
-					null);
-		return fOkStatus;
-	}
-
 	private boolean hasPath(String path) {
 		String currentPath = fSiteArchive != null ? fSiteArchive.getPath() : null;
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/targetdefinition/ImplicitDependenciesSection.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/targetdefinition/ImplicitDependenciesSection.java
index 03487ca..fe3e345 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/targetdefinition/ImplicitDependenciesSection.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/targetdefinition/ImplicitDependenciesSection.java
@@ -18,7 +18,8 @@
 
 import java.util.*;
 import java.util.List;
-import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Status;
 import org.eclipse.equinox.frameworkadmin.BundleInfo;
 import org.eclipse.jface.viewers.*;
 import org.eclipse.jface.window.Window;
@@ -219,7 +220,7 @@
 		List<BundleInfo> targetBundles = new ArrayList<>();
 		TargetBundle[] allTargetBundles = getTarget().getAllBundles();
 		if (allTargetBundles == null || allTargetBundles.length == 0) {
-			throw new CoreException(new Status(IStatus.WARNING, PDEPlugin.getPluginId(), PDEUIMessages.ImplicitDependenciesSection_0));
+			throw new CoreException(Status.warning(PDEUIMessages.ImplicitDependenciesSection_0));
 		}
 		for (int i = 0; i < allTargetBundles.length; i++) {
 			if (!currentBundles.contains(allTargetBundles[i].getBundleInfo().getSymbolicName())) {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/targetdefinition/TargetEditor.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/targetdefinition/TargetEditor.java
index 6e3357c..4564e99 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/targetdefinition/TargetEditor.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/targetdefinition/TargetEditor.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2021 IBM Corporation and others.
+ * Copyright (c) 2005, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -121,7 +121,7 @@
 			showError(PDEUIMessages.TargetEditor_5, e);
 		} catch (ParserConfigurationException | SAXException | IOException e) {
 			setActivePage(fSourceTabIndex);
-			CoreException ce = new CoreException(new Status(IStatus.ERROR, PDEPlugin.getPluginId(), e.getMessage(), e));
+			CoreException ce = new CoreException(Status.error(e.getMessage(), e));
 			showError(PDEUIMessages.TargetEditor_5, ce);
 		}
 	}
@@ -469,7 +469,7 @@
 		private ITargetPlatformService getTargetPlatformService() throws CoreException {
 			ITargetPlatformService service = PDECore.getDefault().acquireService(ITargetPlatformService.class);
 			if (service == null) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, "ITargetPlatformService not available")); //$NON-NLS-1$
+				throw new CoreException(Status.error("ITargetPlatformService not available")); //$NON-NLS-1$
 			}
 			return service;
 		}
@@ -587,6 +587,7 @@
 				}
 				if (fLocationTree != null) {
 					fLocationTree.setInput(getTarget());
+					fLocationTree.setExpandCollapseState(false);
 				}
 				Job.getJobManager().cancel(getJobFamily());
 
@@ -632,6 +633,7 @@
 								}
 								if (fLocationTree != null) {
 									fLocationTree.setInput(getTarget());
+									fLocationTree.setExpandCollapseState(true);
 								}
 								return Status.OK_STATUS;
 							}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/text/XMLTagScanner.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/text/XMLTagScanner.java
index 2200ba7..4dc3d1c 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/text/XMLTagScanner.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/text/XMLTagScanner.java
@@ -37,7 +37,7 @@
 		// Add rule for single and double quotes
 		rules[2] = new MultiLineRule("\"", "\"", fStringToken); //$NON-NLS-1$ //$NON-NLS-2$
 		rules[3] = new SingleLineRule("'", "'", fStringToken); //$NON-NLS-1$ //$NON-NLS-2$
-		rules[4] = new SingleLineRule("<!--", "-->", fCommentToken); //$NON-NLS-1$ //$NON-NLS-2$
+		rules[4] = new MultiLineRule("<!--", "-->", fCommentToken); //$NON-NLS-1$ //$NON-NLS-2$
 		// Add generic whitespace rule.
 		rules[5] = new WhitespaceRule(new XMLWhitespaceDetector());
 		setRules(rules);
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/validation/AbstractControlValidator.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/validation/AbstractControlValidator.java
index f778ccf..6168f42 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/validation/AbstractControlValidator.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/validation/AbstractControlValidator.java
@@ -128,7 +128,7 @@
 	public static int getMessageType(IStatus status) {
 		int severity = status.getSeverity();
 		// Translate severity to the equivalent message provider type
-		if (severity == IStatus.OK) {
+		if (status.isOK()) {
 			return IMessageProvider.NONE;
 		} else if (severity == IStatus.ERROR) {
 			return IMessageProvider.ERROR;
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java
index 6153934..4c3c1b8 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2021 IBM Corporation and others.
+ * Copyright (c) 2005, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -38,6 +38,7 @@
 import org.eclipse.pde.core.plugin.*;
 import org.eclipse.pde.internal.build.IPDEBuildConstants;
 import org.eclipse.pde.internal.core.DependencyManager;
+import org.eclipse.pde.internal.core.DependencyManager.Options;
 import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
 import org.eclipse.pde.internal.launching.launcher.LaunchValidationOperation;
@@ -123,7 +124,7 @@
 				startLevel = levelColumnCache.get(model) != null ? levelColumnCache.get(model).toString() : null;
 				autoStart = autoColumnCache.get(model) != null ? autoColumnCache.get(model).toString() : null;
 			}
-			return BundleLauncherHelper.writeBundleEntry(model, startLevel, autoStart);
+			return BundleLauncherHelper.formatBundleEntry(model, startLevel, autoStart);
 		}
 
 		public Set<String> getNameSet() {
@@ -713,14 +714,24 @@
 		IWorkingSetSelectionDialog dialog = workingSetManager.createWorkingSetSelectionDialog(getShell(), true);
 		if (dialog.open() == Window.OK) {
 			String[] ids = getPluginIDs(dialog.getSelection());
+			ArrayList<IPluginModelBase> newCheckedModels = new ArrayList<>();
+			ArrayList<Object> allCheckedModels = new ArrayList<>();
 			for (String id : ids) {
 				IPluginModelBase model = PluginRegistry.findModel(id);
 				if (model != null) {
 					if (!fPluginTreeViewer.getChecked(model)) {
-						setChecked(model, true);
+						newCheckedModels.add(model);
 					}
 				}
 			}
+			Object[] checkedElements = fPluginTreeViewer.getCheckedElements();
+			allCheckedModels.addAll(Arrays.asList(checkedElements));// previous
+			allCheckedModels.addAll(newCheckedModels);// newly selected
+			fPluginTreeViewer.setCheckedElements(allCheckedModels.toArray());
+			// reset text on newly selected models
+			for (IPluginModelBase iPluginModelBase : newCheckedModels) {
+				resetText(iPluginModelBase);
+			}
 			countSelectedModels();
 		}
 	}
@@ -788,8 +799,10 @@
 		List<IPluginModelBase> toCheck = Arrays.stream(checked).filter(IPluginModelBase.class::isInstance)
 				.map(IPluginModelBase.class::cast).collect(Collectors.toList());
 
-		boolean includeOptional = fIncludeOptionalButton.getSelection();
-		Set<BundleDescription> additionalBundles = DependencyManager.getDependencies(toCheck, includeOptional);
+		DependencyManager.Options[] options = fIncludeOptionalButton.getSelection()
+				? new Options[] { Options.INCLUDE_NON_TEST_FRAGMENTS, Options.INCLUDE_OPTIONAL_DEPENDENCIES }
+				: new Options[] { Options.INCLUDE_NON_TEST_FRAGMENTS };
+		Set<BundleDescription> additionalBundles = DependencyManager.getDependencies(toCheck, options);
 
 		additionalBundles.stream().map(PluginRegistry::findModel).filter(Objects::nonNull).forEach(toCheck::add);
 
@@ -897,10 +910,10 @@
 
 	protected void handleRestoreDefaults() {
 		TreeSet<String> wtable = new TreeSet<>();
-
+		ArrayList<IPluginModelBase> checkedModels = new ArrayList<>();
 		for (int i = 0; i < getWorkspaceModels().length; i++) {
 			IPluginModelBase model = getWorkspaceModels()[i];
-			fPluginTreeViewer.setChecked(model, true);
+			checkedModels.add(model);
 			String id = model.getPluginBase().getId();
 			if (id != null) {
 				wtable.add(model.getPluginBase().getId());
@@ -911,9 +924,10 @@
 		for (IPluginModelBase model : externalModels) {
 			boolean masked = wtable.contains(model.getPluginBase().getId());
 			if (!masked && model.isEnabled()) {
-				fPluginTreeViewer.setChecked(model, true);
+				checkedModels.add(model);
 			}
 		}
+		fPluginTreeViewer.setCheckedElements(checkedModels.toArray());
 		countSelectedModels();
 
 		Object[] selected = fPluginTreeViewer.getCheckedElements();
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/ConfigurationTemplateBlock.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/ConfigurationTemplateBlock.java
index a35ac1f..e5fb8b9 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/ConfigurationTemplateBlock.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/ConfigurationTemplateBlock.java
@@ -13,8 +13,6 @@
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.launcher;
 
-import org.eclipse.pde.launching.IPDELauncherConstants;
-
 import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;
 
 import java.io.File;
@@ -29,6 +27,7 @@
 import org.eclipse.pde.internal.ui.PDEPlugin;
 import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.pde.internal.ui.util.FileNameFilter;
+import org.eclipse.pde.launching.IPDELauncherConstants;
 import org.eclipse.pde.ui.launcher.AbstractLauncherTab;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.GridData;
@@ -133,10 +132,10 @@
 		dialog.setTitle(PDEUIMessages.ConfigurationTab_fileSelection);
 		dialog.setMessage(PDEUIMessages.ConfigurationTab_fileDialogMessage);
 		dialog.setValidator(selection -> {
-			if (selection.length > 0 && selection[0] instanceof IFile)
-				return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", null); //$NON-NLS-1$
-
-			return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, "", null); //$NON-NLS-1$
+			if (selection.length > 0 && selection[0] instanceof IFile) {
+				return Status.OK_STATUS;
+			}
+			return Status.error(""); //$NON-NLS-1$
 		});
 		if (dialog.open() == Window.OK) {
 			file = (IFile) dialog.getFirstResult();
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/FeatureBlock.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/FeatureBlock.java
index d3ab3f6..b4760fc 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/FeatureBlock.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/FeatureBlock.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2010, 2018 IBM Corporation and others.
+ * Copyright (c) 2010, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -338,10 +338,10 @@
 				// Unlike PluginBlock, we don't want to validate the application/product requirements because we will grab them automatically at launch time
 				fOperation = new LaunchValidationOperation(fLaunchConfig) {
 					@Override
-					protected IPluginModelBase[] getModels() throws CoreException {
+					protected Set<IPluginModelBase> getModels() throws CoreException {
 						// The feature block is used in both the OSGi config and Eclipse configs, use the tab id to determine which we are using
 						boolean isOSGiTab = fTab.getId().equals(IPDELauncherConstants.TAB_BUNDLES_ID);
-						return BundleLauncherHelper.getMergedBundles(fLaunchConfiguration, isOSGiTab);
+						return BundleLauncherHelper.getMergedBundleMap(fLaunchConfiguration, isOSGiTab).keySet();
 					}
 				};
 			try {
@@ -698,13 +698,6 @@
 		public String getPluginModelVersion() {
 			return fPluginModelBase.getPluginBase().getVersion();
 		}
-
-		public String buildEntry(boolean isChecked) {
-			IPluginBase base = fPluginModelBase.getPluginBase();
-			return String.join(":", base.getId(), base.getVersion(), fPluginResolution, String.valueOf(isChecked), //$NON-NLS-1$
-					fStartLevel, fAutoStart);
-		}
-
 	}
 
 	static class FeatureLaunchModel {
@@ -1179,29 +1172,31 @@
 
 		for (Object model : models) {
 			if (model instanceof FeatureLaunchModel) {
-				FeatureLaunchModel featureModel = (FeatureLaunchModel) model;
-				StringBuilder buffer = new StringBuilder();
-				buffer.append(featureModel.getId());
-				buffer.append(':');
-				buffer.append(featureModel.getResolutionValue());
-				featuresEntry.add(buffer.toString());
+				FeatureLaunchModel feature = (FeatureLaunchModel) model;
+				String entry = BundleLauncherHelper.formatFeatureEntry(feature.getId(), feature.getResolutionValue());
+				featuresEntry.add(entry);
 			} else if (model instanceof PluginLaunchModel) {
 				PluginLaunchModel pluginLaunchModel = (PluginLaunchModel) model;
-				pluginsEntry.add(pluginLaunchModel.buildEntry(true));
+				pluginsEntry.add(buildAdditionalPluginEntry(pluginLaunchModel, true));
 				checkPluginLaunchModels.add(pluginLaunchModel);
 			}
 		}
 
 		for (PluginLaunchModel uncheckedPluginLaunchModel : fAdditionalPlugins) {
-			if (checkPluginLaunchModels.contains(uncheckedPluginLaunchModel))
-				continue;
-			pluginsEntry.add(uncheckedPluginLaunchModel.buildEntry(false));
+			if (!checkPluginLaunchModels.contains(uncheckedPluginLaunchModel)) {
+				pluginsEntry.add(buildAdditionalPluginEntry(uncheckedPluginLaunchModel, false));
+			}
 		}
 		config.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, featuresEntry);
 		config.setAttribute(IPDELauncherConstants.ADDITIONAL_PLUGINS, pluginsEntry);
 
 	}
 
+	private static String buildAdditionalPluginEntry(PluginLaunchModel pl, boolean isChecked) {
+		return BundleLauncherHelper.formatAdditionalPluginEntry(pl.getPluginModelBase(), pl.getPluginResolution(),
+				isChecked, pl.getStartLevel(), pl.getAutoStart());
+	}
+
 	private void saveSortOrder() {
 		PDEPreferencesManager prefs = new PDEPreferencesManager(IPDEUIConstants.PLUGIN_ID);
 		Tree tree = fTree.getTree();
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/LaunchAction.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/LaunchAction.java
index a5763fc..d6477c1 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/LaunchAction.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/LaunchAction.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2016 IBM Corporation and others.
+ * Copyright (c) 2005, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -14,10 +14,12 @@
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.launcher;
 
-import static java.util.stream.Collectors.toSet;
+import static org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper.formatAdditionalPluginEntry;
+import static org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper.formatFeatureEntry;
 
 import java.io.File;
 import java.util.*;
+import java.util.stream.Collectors;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.*;
 import org.eclipse.debug.core.*;
@@ -132,22 +134,25 @@
 	}
 
 	private void refreshFeatureLaunchAttributes(ILaunchConfigurationWorkingCopy wc, IPluginModelBase[] models) {
-		Set<String> selectedFeatures = Arrays.stream(getUniqueFeatures())
-				.map(f -> f.getFeature().getId() + ":" + IPDELauncherConstants.LOCATION_DEFAULT).collect(toSet()); //$NON-NLS-1$
+		FeatureModelManager fmm = PDECore.getDefault().getFeatureModelManager();
+		Set<String> selectedFeatures = Arrays.stream(fProduct.getFeatures()) //
+				.map(f -> fmm.findFeatureModel(f.getId(), f.getVersion())).filter(Objects::nonNull)
+				.map(m -> formatFeatureEntry(m.getFeature().getId(), IPDELauncherConstants.LOCATION_DEFAULT))
+				.collect(Collectors.toCollection(LinkedHashSet::new));
 
 		Set<String> additionalPlugins = Arrays.stream(models)
 				.map(model -> getPluginConfiguration(model)
-						.map(c -> new FeatureBlock.PluginLaunchModel(model, c, null).buildEntry(true)).orElse(null))
-				.filter(Objects::nonNull).collect(toSet());
+						.map(c -> formatAdditionalPluginEntry(model, c.fResolution, true, c.fStartLevel, c.fAutoStart))
+						.orElse(null))
+				.filter(Objects::nonNull).collect(Collectors.toCollection(LinkedHashSet::new));
 
 		wc.setAttribute(IPDELauncherConstants.SELECTED_FEATURES, selectedFeatures);
 		wc.setAttribute(IPDELauncherConstants.ADDITIONAL_PLUGINS, additionalPlugins);
 	}
 
 	private void appendBundle(Set<String> plugins, IPluginModelBase model) {
-		AdditionalPluginData configuration = getPluginConfiguration(model).orElse(FeatureBlock.DEFAULT_PLUGIN_DATA);
-		String entry = BundleLauncherHelper.writeBundleEntry(model, configuration.fStartLevel,
-				configuration.fAutoStart);
+		AdditionalPluginData config = getPluginConfiguration(model).orElse(FeatureBlock.DEFAULT_PLUGIN_DATA);
+		String entry = BundleLauncherHelper.formatBundleEntry(model, config.fStartLevel, config.fAutoStart);
 		plugins.add(entry);
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/OSGiBundleBlock.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/OSGiBundleBlock.java
index 1cdb967..099e2ad 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/OSGiBundleBlock.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/OSGiBundleBlock.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2015 IBM Corporation and others.
+ * Copyright (c) 2005, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -14,7 +14,6 @@
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.launcher;
 
-import java.util.HashMap;
 import java.util.Map;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.debug.core.ILaunchConfiguration;
@@ -74,10 +73,7 @@
 	// TODO deal with the discrepency between save/init states of the two blocks
 
 	private void initializePluginsState(ILaunchConfiguration configuration) throws CoreException {
-		Map<IPluginModelBase, String> selected = new HashMap<>();
-		selected.putAll(BundleLauncherHelper.getWorkspaceBundleMap(configuration));
-		selected.putAll(BundleLauncherHelper.getTargetBundleMap(configuration, null));
-
+		Map<IPluginModelBase, String> selected = BundleLauncherHelper.getAllSelectedPluginBundles(configuration);
 		initializePluginsState(selected);
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/PluginBlock.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/PluginBlock.java
index cb64cc6..3d355a6 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/PluginBlock.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/PluginBlock.java
@@ -94,10 +94,7 @@
 	}
 
 	private void initializePluginsState(ILaunchConfiguration config) throws CoreException {
-		Map<IPluginModelBase, String> selected = new HashMap<>();
-		selected.putAll(BundleLauncherHelper.getWorkspaceBundleMap(config, null));
-		selected.putAll(BundleLauncherHelper.getTargetBundleMap(config, null));
-
+		Map<IPluginModelBase, String> selected = BundleLauncherHelper.getAllSelectedPluginBundles(config);
 		initializePluginsState(selected);
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/nls/NLSFragmentGenerator.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/nls/NLSFragmentGenerator.java
index 244d793..0848958 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/nls/NLSFragmentGenerator.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/nls/NLSFragmentGenerator.java
@@ -455,9 +455,8 @@
 		}
 		}
 		catch (IOException e) {
-			throw new CoreException(new Status(IStatus.ERROR, PDEPlugin.getPluginId(),
-					MessageFormat.format("IOException while processing: {0}", fragmentProject.getName()), //$NON-NLS-1$
-					null));
+			throw new CoreException(
+					Status.error(MessageFormat.format("IOException while processing: {0}", fragmentProject.getName()))); //$NON-NLS-1$
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/parts/PluginVersionPart.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/parts/PluginVersionPart.java
index 949e961..7520574 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/parts/PluginVersionPart.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/parts/PluginVersionPart.java
@@ -214,7 +214,7 @@
 	private IStatus validateVersion(String text, Text textWidget, boolean shortErrorMessage) {
 		if (text.length() == 0)
 			return Status.OK_STATUS;
-		if (VersionUtil.validateVersion(text).getSeverity() != IStatus.OK) {
+		if (!VersionUtil.validateVersion(text).isOK()) {
 			String errorMessage = null;
 			if (shortErrorMessage) {
 				// For dialogs
@@ -223,8 +223,8 @@
 				// For everything else:  Field assist, wizards
 				errorMessage = UtilMessages.BundleErrorReporter_InvalidFormatInBundleVersion;
 			}
-			return new Status(IStatus.ERROR, "org.eclipse.pde.ui", //$NON-NLS-1$
-					IStatus.ERROR, PDELabelUtility.qualifyMessage(PDELabelUtility.getFieldLabel(textWidget), errorMessage), null);
+			return Status.error(PDELabelUtility.qualifyMessage(PDELabelUtility.getFieldLabel(textWidget), errorMessage),
+					null);
 		}
 
 		return Status.OK_STATUS;
@@ -250,8 +250,8 @@
 		try {
 			v1 = new Version(getMinVersion());
 		} catch (IllegalArgumentException e) {
-			return new Status(IStatus.ERROR, "org.eclipse.pde.ui", //$NON-NLS-1$
-					IStatus.ERROR, PDELabelUtility.qualifyMessage(PDELabelUtility.getFieldLabel(fMinVersionText), errorMessage), null);
+			return Status.error(
+					PDELabelUtility.qualifyMessage(PDELabelUtility.getFieldLabel(fMinVersionText), errorMessage));
 		}
 		if (!fRangeAllowed) // version created fine
 			return Status.OK_STATUS;
@@ -259,15 +259,14 @@
 		try {
 			v2 = new Version(getMaxVersion());
 		} catch (IllegalArgumentException e) {
-			return new Status(IStatus.ERROR, "org.eclipse.pde.ui", //$NON-NLS-1$
-					IStatus.ERROR, PDELabelUtility.qualifyMessage(PDELabelUtility.getFieldLabel(fMaxVersionText), errorMessage), null);
+			return Status.error(
+					PDELabelUtility.qualifyMessage(PDELabelUtility.getFieldLabel(fMaxVersionText), errorMessage));
 		}
 		if (v1.compareTo(v2) == 0 || v1.compareTo(v2) < 0) {
 			fIsRanged = true;
 			return Status.OK_STATUS;
 		}
-		return new Status(IStatus.ERROR, "org.eclipse.pde.ui", //$NON-NLS-1$
-				IStatus.ERROR, PDEUIMessages.DependencyPropertiesDialog_versionRangeError, null);
+		return Status.error(PDEUIMessages.DependencyPropertiesDialog_versionRangeError);
 	}
 
 	/**
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties
index 1f32c8a..b4eb7f2 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/pderesources.properties
@@ -376,6 +376,7 @@
 MainPreferencePage_updateStale=&Update stale manifest files prior to launching
 MainPreferencePage_WorkspacePluginsOverrideTarget=Workspace p&lug-ins override target platform plug-ins with the same id
 MainPreferencePage_DisableAPIAnalysisBuilder=&Disable API analysis builder
+MainPreferencePage_RunAPIAnalysisBuilderAsJob=&Run API analysis parallel to the build job
 MainPreferencePage_WorkspacePluginsOverrideTargetTooltip=When disabled, all plug-in versions from workspace and target platform are being used.
 MainPreferencePage_test_plugin_pattern_group=Test plug-in detection:
 MainPreferencePage_test_plugin_pattern_description=Source folders in test plug-ins are marked to contain test sources.
@@ -441,7 +442,7 @@
 SchemaEditor_SpecSection_plugin = Plug-in ID:
 SchemaEditor_SpecSection_point = Point ID:
 SchemaAttributeDetails_extends=Extends:
-SchemaEditor_SpecSection_name = Point Name:
+SchemaEditor_SpecSection_name = Point name:
 
 SchemaEditor_ElementSection_title = Extension Point Elements
 SchemaEditor_ElementSection_remove=Remove
@@ -482,7 +483,7 @@
 SchemaDetails_deprecated=Deprecated:
 SchemaDetails_internal=Internal:
 SchemaIncludesSection_removeButton=Remove
-SchemaIdentifierAttributeDetails_additionalRestrictions=Additional Restrictions:
+SchemaIdentifierAttributeDetails_additionalRestrictions=Additional restrictions:
 
 SchemaEditor_FormPage_title = Definition
 SchemaIncludesSection_title=Schema Inclusions
@@ -711,7 +712,7 @@
 ExternalizeStringsWizardPage_keyDuplicateError=New key may not be a duplicate of another key
 ExternalizeStringsWizardPage_keySuggested=\n\tsuggested key value:
 
-Preferences_TargetEnvironmentPage_os = &Operating System:
+Preferences_TargetEnvironmentPage_os = &Operating system:
 Preferences_TargetEnvironmentPage_ws = &Windowing System:
 Preferences_TargetEnvironmentPage_nl = &Locale:
 Preferences_TargetEnvironmentPage_arch = Arc&hitecture:
@@ -774,7 +775,7 @@
 FragmentContentPage_pid = &Plug-in ID:
 FragmentContentPage_pversion = Pl&ug-in Version:
 ContentPage_browse = Bro&wse...
-ContentPage_matchRule = &Match Rule:
+ContentPage_matchRule = &Match rule:
 ContentPage_noid = ID is not set
 ContentPage_invalidId = Invalid ID.  Legal characters are A-Z a-z 0-9 . _ -
 CommandList_groupName=Commands
@@ -853,7 +854,7 @@
 NewProjectCreationPage_ptarget=This plug-in is targeted to run with:
 NewProjectCreationPage_pDependsOnRuntime=&Eclipse
 NewProjectCreationPage_environmentsButton=Envi&ronments...
-NewProjectCreationPage_executionEnvironments_label=&Execution Environment:
+NewProjectCreationPage_executionEnvironments_label=&Execution environment:
 NewProjectCreationPage_invalidProjectName=Project name cannot contain %
 NewProjectCreationPage_invalidLocationPath=Location path cannot contain %
 NewProjectCreationPage_invalidEE=Execution Environment isn't compatible with any installed JRE
@@ -863,8 +864,8 @@
 
 AbstractTemplateSection_generating = Generating content...
 AbstractLauncherToolbar_noProblems=No problems were detected.
-AbstractSchemaDetails_minOccurLabel=Min Occurrences:
-AbstractSchemaDetails_maxOccurLabel=Max Occurrences:
+AbstractSchemaDetails_minOccurLabel=Min occurrences:
+AbstractSchemaDetails_maxOccurLabel=Max occurrences:
 AbstractLauncherToolbar_noSelection=No {0} are selected.
 AbstractLauncherToolbar_noSelection_plugins=No Plug-ins are selected.
 AbstractLauncherToolbar_noSelection_bundles=No Bundles are selected.
@@ -1436,8 +1437,8 @@
 DependencyPropertiesDialog_invalidFormat=The specified version is invalid
 DependencyPropertiesDialog_comboInclusive=Inclusive
 DependencyPropertiesDialog_comboExclusive=Exclusive
-DependencyPropertiesDialog_minimumVersion=&Minimum Version:
-DependencyPropertiesDialog_maximumVersion=Ma&ximum Version:
+DependencyPropertiesDialog_minimumVersion=&Minimum version:
+DependencyPropertiesDialog_maximumVersion=Ma&ximum version:
 DependencyPropertiesDialog_exportGroupText=Version exported
 DependencyPropertiesDialog_closeButtonLabel=&Close
 DependencyAnalysisSection_plugin_notEditable = <form>\
@@ -1738,7 +1739,7 @@
 ######### Target Export Wizard ###################################33
 ExportActiveTargetDefinition = Exporting Target Definition
 ExportActiveTargetDefinition_message = Please choose a destination directory to export all target content
-ExportTargetCurrentTarget = Active &Target:
+ExportTargetCurrentTarget = Active &target:
 ExportTargetChooseFolder = &Destination:
 ExportTargetBrowse = Bro&wse...
 ExportTargetSelectDestination = Select Destination
@@ -1809,7 +1810,7 @@
 GeneralInfoSection_name=Name:
 GeneralInfoSection_class=Activator:
 GeneralInfoSection_browse=Browse...
-GeneralInfoSection_platformFilter=Platform Filter:
+GeneralInfoSection_platformFilter=Platform filter:
 GeneralInfoSection_selectionTitle=Select Type
 RequiresSection_title=Required Plug-ins
 RequiresSection_fDesc=Specify the list of plug-ins required for the operation of this fragment.
@@ -2177,7 +2178,7 @@
 
 ProductJRESection_title=Execution Environment
 ProductJRESection_desc=Specify the execution environment of the product. The respective default JRE will be bundled with the product.
-ProductJRESection_eeName=Execution Environment:
+ProductJRESection_eeName=Execution environment:
 ProductJRESection_browseEEs=Environments...
 ProdctJRESection_bundleJRE=Bundle JRE for this environment with the product.
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/MainPreferencePage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/MainPreferencePage.java
index 2a17ee0..78de1b0 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/MainPreferencePage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/MainPreferencePage.java
@@ -38,6 +38,7 @@
 import org.eclipse.pde.internal.ui.launcher.BaseBlock;
 import org.eclipse.pde.internal.ui.shared.target.TargetStatus;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.*;
@@ -140,6 +141,7 @@
 	private Button fShowTargetStatus;
 	private Button fAlwaysPreferWorkspace;
 	private Button fDisableAPIAnalysisBuilder;
+	private Button fRunAPIAnalysisBuilderAsJob;
 	private Button fAddSwtNonDisposalReporting;
 
 	private Text fRuntimeWorkspaceLocation;
@@ -199,6 +201,15 @@
 		fDisableAPIAnalysisBuilder.setText(PDEUIMessages.MainPreferencePage_DisableAPIAnalysisBuilder);
 		fDisableAPIAnalysisBuilder.setSelection(store.getBoolean(IPreferenceConstants.DISABLE_API_ANALYSIS_BUILDER));
 
+		fRunAPIAnalysisBuilderAsJob = new Button(optionComp, SWT.CHECK);
+		fRunAPIAnalysisBuilderAsJob.setText(PDEUIMessages.MainPreferencePage_RunAPIAnalysisBuilderAsJob);
+		fRunAPIAnalysisBuilderAsJob.setSelection(
+				PDECore.getDefault().getPreferencesManager().getBoolean(ICoreConstants.RUN_API_ANALYSIS_AS_JOB));
+
+		fDisableAPIAnalysisBuilder.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+			fRunAPIAnalysisBuilderAsJob.setEnabled(!fDisableAPIAnalysisBuilder.getSelection());
+		}));
+
 		fAddSwtNonDisposalReporting = new Button(optionComp, SWT.CHECK);
 		fAddSwtNonDisposalReporting.setText(PDEUIMessages.MainPreferencePage_AddSwtNonDisposedToVMArguments);
 		fAddSwtNonDisposalReporting
@@ -381,13 +392,20 @@
 
 		}
 
+		boolean runAPIAnalysisAsJob = fRunAPIAnalysisBuilderAsJob.getSelection();
+		if (PDECore.getDefault().getPreferencesManager()
+				.getBoolean(ICoreConstants.RUN_API_ANALYSIS_AS_JOB) != runAPIAnalysisAsJob) {
+			PDEPreferencesManager prefs = PDECore.getDefault().getPreferencesManager();
+			prefs.setValue(ICoreConstants.RUN_API_ANALYSIS_AS_JOB, runAPIAnalysisAsJob);
+		}
+
 		boolean addSwtNonDisposalReporting = fAddSwtNonDisposalReporting.getSelection();
 		if (store.getBoolean(IPreferenceConstants.ADD_SWT_NON_DISPOSAL_REPORTING) != addSwtNonDisposalReporting) {
 			store.setValue(IPreferenceConstants.ADD_SWT_NON_DISPOSAL_REPORTING, addSwtNonDisposalReporting);
 			PDEPreferencesManager prefs = PDECore.getDefault().getPreferencesManager();
 			prefs.setValue(ICoreConstants.ADD_SWT_NON_DISPOSAL_REPORTING, addSwtNonDisposalReporting);
 		}
-
+		PDECore.getDefault().getPreferencesManager().savePluginPreferences();
 		PDEPlugin.getDefault().getPreferenceManager().savePluginPreferences();
 
 		PDEPreferencesManager launchingStore = PDELaunchingPlugin.getDefault().getPreferenceManager();
@@ -423,6 +441,9 @@
 		fAddToJavaSearch.setSelection(store.getDefaultBoolean(IPreferenceConstants.ADD_TO_JAVA_SEARCH));
 		fShowTargetStatus.setSelection(store.getDefaultBoolean(IPreferenceConstants.SHOW_TARGET_STATUS));
 		fAlwaysPreferWorkspace.setSelection(store.getDefaultBoolean(IPreferenceConstants.WORKSPACE_PLUGINS_OVERRIDE_TARGET));
+		fRunAPIAnalysisBuilderAsJob.setEnabled(true);
+		fRunAPIAnalysisBuilderAsJob.setSelection(
+				PDECore.getDefault().getPreferencesManager().getDefaultBoolean(ICoreConstants.RUN_API_ANALYSIS_AS_JOB));
 		fDisableAPIAnalysisBuilder.setSelection(store.getDefaultBoolean(IPreferenceConstants.DISABLE_API_ANALYSIS_BUILDER));
 		fAddSwtNonDisposalReporting
 				.setSelection(store.getDefaultBoolean(IPreferenceConstants.ADD_SWT_NON_DISPOSAL_REPORTING));
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/ProjectSelectionDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/ProjectSelectionDialog.java
index 82be65d..7109870 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/ProjectSelectionDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/ProjectSelectionDialog.java
@@ -16,7 +16,8 @@
 import java.util.HashSet;
 import java.util.Set;
 import org.eclipse.core.resources.ResourcesPlugin;
-import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Status;
 import org.eclipse.jdt.core.*;
 import org.eclipse.jdt.ui.*;
 import org.eclipse.jface.dialogs.Dialog;
@@ -151,10 +152,10 @@
 	 */
 	private void doSelectionChanged(Object[] objects) {
 		if (objects.length != 1) {
-			updateStatus(new Status(IStatus.ERROR, PDEPlugin.getPluginId(), "")); //$NON-NLS-1$
+			updateStatus(Status.error("")); //$NON-NLS-1$
 			setSelectionResult(null);
 		} else {
-			updateStatus(new Status(IStatus.OK, PDEPlugin.getPluginId(), "")); //$NON-NLS-1$
+			updateStatus(Status.OK_STATUS);
 			setSelectionResult(objects);
 		}
 	}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/TargetPlatformPreferencePage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/TargetPlatformPreferencePage.java
index 4a45192..70c449d 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/TargetPlatformPreferencePage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/preferences/TargetPlatformPreferencePage.java
@@ -937,7 +937,7 @@
 								fDetails.refresh(true);
 					});
 
-					if (event.getResult().getSeverity() == IStatus.OK) {
+					if (event.getResult().isOK()) {
 						if (fActiveTarget != null) {
 							PDEPreferencesManager pref = new PDEPreferencesManager(PDEPlugin.getPluginId());
 							if (pref.getBoolean(IPreferenceConstants.ADD_TO_JAVA_SEARCH)) {
@@ -976,7 +976,7 @@
 		} else {
 			// Manually update the active target and status line to update name, resolve status, and errors
 			if (fActiveTarget != null) {
-				((TargetPlatformService) service).setWorkspaceTargetDefinition(fActiveTarget);
+				((TargetPlatformService) service).setWorkspaceTargetDefinition(fActiveTarget, false);
 				TargetStatus.refreshTargetStatusContent();
 			}
 		}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/AddNewDependenciesAction.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/AddNewDependenciesAction.java
index 20c4206..fc80f74 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/AddNewDependenciesAction.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/AddNewDependenciesAction.java
@@ -65,7 +65,7 @@
 				} finally {
 					monitor.done();
 				}
-				return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", null); //$NON-NLS-1$
+				return Status.OK_STATUS;
 			}
 		};
 		job.setUser(true);
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/CalculateUsesAction.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/CalculateUsesAction.java
index df9952e..6b92619 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/CalculateUsesAction.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/CalculateUsesAction.java
@@ -22,7 +22,8 @@
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jface.action.Action;
 import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
-import org.eclipse.pde.internal.ui.*;
+import org.eclipse.pde.internal.ui.PDEPluginImages;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.progress.IProgressConstants;
 
@@ -56,7 +57,7 @@
 				} finally {
 					monitor.done();
 				}
-				return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", null); //$NON-NLS-1$
+				return Status.OK_STATUS;
 			}
 		};
 	}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/UnusedDependenciesJob.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/UnusedDependenciesJob.java
index ab166ab..6b2c590 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/UnusedDependenciesJob.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dependencies/UnusedDependenciesJob.java
@@ -18,8 +18,6 @@
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jface.action.Action;
 import org.eclipse.pde.core.plugin.IPluginModelBase;
-import org.eclipse.pde.internal.ui.PDEPlugin;
-import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.swt.widgets.Display;
 
 public class UnusedDependenciesJob extends Job {
@@ -44,7 +42,7 @@
 		} finally {
 			monitor.done();
 		}
-		return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, PDEUIMessages.UnusedDependenciesJob_viewResults, null);
+		return Status.OK_STATUS;
 	}
 
 	private Action getShowResultsAction(Object[] unused) {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dialogs/FilteredIUSelectionDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dialogs/FilteredIUSelectionDialog.java
index 8abc10f..ece4597 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dialogs/FilteredIUSelectionDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dialogs/FilteredIUSelectionDialog.java
@@ -26,7 +26,6 @@
 import org.eclipse.jface.dialogs.DialogSettings;
 import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.jface.viewers.*;
-import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.target.Messages;
 import org.eclipse.pde.internal.core.target.P2TargetUtils;
 import org.eclipse.pde.internal.ui.*;
@@ -204,7 +203,7 @@
 		// TODO clean up this code a bit...
 		IMetadataRepositoryManager manager = P2TargetUtils.getRepoManager();
 		if (manager == null)
-			throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.IUBundleContainer_2));
+			throw new CoreException(Status.error(Messages.IUBundleContainer_2));
 
 		//URI[] knownRepositories = metadataManager.getKnownRepositories(IRepositoryManager.REPOSITORIES_ALL);
 		IQuery<IInstallableUnit> pipedQuery;
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dialogs/FilteredPluginArtifactsSelectionDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dialogs/FilteredPluginArtifactsSelectionDialog.java
index 8eeadbe..1675e41 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dialogs/FilteredPluginArtifactsSelectionDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/search/dialogs/FilteredPluginArtifactsSelectionDialog.java
@@ -478,7 +478,7 @@
 
 	@Override
 	protected IStatus validateItem(Object item) {
-		return new Status(IStatus.OK, "org.eclipse.pde.ui", 0, "", null); //$NON-NLS-1$ //$NON-NLS-2$
+		return Status.OK_STATUS;
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/AddBundleContainerSelectionPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/AddBundleContainerSelectionPage.java
index 4dadced..a841830 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/AddBundleContainerSelectionPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/AddBundleContainerSelectionPage.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2009, 2018 IBM Corporation and others.
+ * Copyright (c) 2009, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Christoph Läubrich - Bug 577861
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.shared.target;
 
@@ -391,6 +392,31 @@
 						}
 						return true;
 					}
+
+					@Override
+					public boolean canFinish() {
+						if (fWizard != null) {
+							return fWizard.canFinish();
+						}
+						return true;
+					}
+
+					@Override
+					public IWizardPage getNextPage(IWizardPage page) {
+						if (fWizard != null) {
+							return fWizard.getNextPage(page);
+						}
+						return super.getNextPage(page);
+					}
+
+					@Override
+					public IWizardPage getPreviousPage(IWizardPage page) {
+						if (fWizard != null) {
+							return fWizard.getPreviousPage(page);
+						}
+						return super.getPreviousPage(page);
+					}
+
 				};
 				wizard.setContainer(getContainer());
 				wizard.setWindowTitle(Messages.AddBundleContainerSelectionPage_1);
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/AddFeatureContainersPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/AddFeatureContainersPage.java
index 633686d..b919b53 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/AddFeatureContainersPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/AddFeatureContainersPage.java
@@ -261,11 +261,11 @@
 		try (InputStream stream = new BufferedInputStream(new FileInputStream(manifest));) {
 			model.load(stream, false);
 			if (!model.isValid()) {
-				status = new Status(IStatus.WARNING, IPDEUIConstants.PLUGIN_ID, IStatus.OK, NLS.bind(Messages.FeatureImportWizardPage_importHasInvalid, dir), null);
+				status = Status.warning(NLS.bind(Messages.FeatureImportWizardPage_importHasInvalid, dir), null);
 			}
 		} catch (Exception e) {
 			// Errors in the file
-			status = new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, IStatus.OK, e.getMessage(), e);
+			status = Status.error(e.getMessage(), e);
 		}
 		if (status == null)
 			result.add(model);
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/ArgumentsFromContainerSelectionDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/ArgumentsFromContainerSelectionDialog.java
index adbe953..46b9ed3 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/ArgumentsFromContainerSelectionDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/ArgumentsFromContainerSelectionDialog.java
@@ -17,14 +17,14 @@
 
 import java.util.*;
 import java.util.List;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.TrayDialog;
 import org.eclipse.jface.viewers.*;
 import org.eclipse.pde.core.target.ITargetDefinition;
 import org.eclipse.pde.core.target.ITargetLocation;
-import org.eclipse.pde.internal.ui.*;
+import org.eclipse.pde.internal.ui.IHelpContextIds;
+import org.eclipse.pde.internal.ui.SWTFactory;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
@@ -76,7 +76,7 @@
 						fAllArguments.put(container, args);
 						foundArguments = true;
 					} else {
-						fAllArguments.put(container, new Object[] {new Status(IStatus.ERROR, PDEPlugin.getPluginId(), Messages.ArgumentsFromContainerSelectionDialog_1)});
+						fAllArguments.put(container, new Object[] { Status.error(Messages.ArgumentsFromContainerSelectionDialog_1) });
 					}
 				}
 			}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditDirectoryContainerPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditDirectoryContainerPage.java
index 42ed44d..295eb40 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditDirectoryContainerPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditDirectoryContainerPage.java
@@ -348,7 +348,7 @@
 		if (fTargetService == null) {
 			fTargetService = PDECore.getDefault().acquireService(ITargetPlatformService.class);
 			if (fTargetService == null) {
-				throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, Messages.AddDirectoryContainerPage_9));
+				throw new CoreException(Status.error(Messages.AddDirectoryContainerPage_9));
 			}
 		}
 		return fTargetService;
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditIUContainerPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditIUContainerPage.java
index 02594b8..c723fa6 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditIUContainerPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditIUContainerPage.java
@@ -57,7 +57,7 @@
 public class EditIUContainerPage extends WizardPage implements IEditBundleContainerPage {
 
 	// Status for any errors on the page
-	private static final IStatus BAD_IU_SELECTION = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), Messages.EditIUContainerPage_0);
+	private static final IStatus BAD_IU_SELECTION = Status.error(Messages.EditIUContainerPage_0);
 	private IStatus fSelectedIUStatus = Status.OK_STATUS;
 
 	// Dialog settings
@@ -138,7 +138,7 @@
 	public ITargetLocation getBundleContainer() {
 		ITargetPlatformService service = PDECore.getDefault().acquireService(ITargetPlatformService.class);
 		if (service == null) {
-			PDEPlugin.log(new Status(IStatus.ERROR, PDEPlugin.getPluginId(), Messages.EditIUContainerPage_9));
+			PDEPlugin.log(Status.error(Messages.EditIUContainerPage_9));
 		}
 		int flags = fIncludeRequiredButton.getSelection() ? IUBundleContainer.INCLUDE_REQUIRED : 0;
 		flags |= fAllPlatformsButton.getSelection() ? IUBundleContainer.INCLUDE_ALL_ENVIRONMENTS : 0;
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/Messages.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/Messages.java
index bedd853..13fe231 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/Messages.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/Messages.java
@@ -61,6 +61,8 @@
 	public static String BundleContainerTable_Btn_Text_Remove;
 	public static String BundleContainerTable_Btn_Text_Update;
 	public static String BundleContainerTable_Btn_Text_Reload;
+	public static String BundleContainerTable_Btn_Text_ExpandAll;
+	public static String BundleContainerTable_Btn_Text_CollapseAll;
 	public static String BundleContainerTable_8;
 	public static String BundleContainerTable_9;
 	public static String BundleContainerTable_10;
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/TargetContentsGroup.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/TargetContentsGroup.java
index 3628b6b..c5b9a69 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/TargetContentsGroup.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/TargetContentsGroup.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2009, 2021 IBM Corporation and others.
+ * Copyright (c) 2009, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -713,7 +713,10 @@
 			subMonitor.worked(10);
 
 			// Get all dependency bundles
-			DependencyManager.getDependencies(checkedModels, implicitDependencies, state.getState(), true)
+			DependencyManager
+					.getDependencies(checkedModels, implicitDependencies, state.getState(),
+							DependencyManager.Options.INCLUDE_OPTIONAL_DEPENDENCIES,
+							DependencyManager.Options.INCLUDE_ALL_FRAGMENTS)
 					.forEach(d -> dependencies.add(d.getSymbolicName()));
 			subMonitor.worked(50);
 		};
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/TargetLocationsGroup.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/TargetLocationsGroup.java
index e64edb6..4442f83 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/TargetLocationsGroup.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/TargetLocationsGroup.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2009, 2020 IBM Corporation and others.
+ * Copyright (c) 2009, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -38,8 +38,7 @@
 import org.eclipse.pde.internal.ui.wizards.target.TargetDefinitionContentPage;
 import org.eclipse.pde.ui.target.ITargetLocationHandler;
 import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.KeyAdapter;
-import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.*;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.*;
@@ -90,6 +89,7 @@
 	private Button fRemoveButton;
 	private Button fUpdateButton;
 	private Button fReloadButton;
+	private Button fExpandCollapseButton;
 	private Button fShowContentButton;
 
 	private ITargetDefinition fTarget;
@@ -191,6 +191,7 @@
 		fUpdateButton.setToolTipText(Messages.TargetLocationsGroup_update);
 		fReloadButton = toolkit.createButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Reload, SWT.PUSH);
 		fReloadButton.setToolTipText(Messages.TargetLocationsGroup_reload);
+		fExpandCollapseButton = toolkit.createButton(buttonComp, Messages.BundleContainerTable_Btn_Text_ExpandAll, SWT.PUSH);
 
 		fShowContentButton = toolkit.createButton(comp, Messages.TargetLocationsGroup_1, SWT.CHECK);
 
@@ -229,6 +230,7 @@
 		fRemoveButton = SWTFactory.createPushButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Remove, null);
 		fUpdateButton = SWTFactory.createPushButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Update, null);
 		fReloadButton = SWTFactory.createPushButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Reload, null);
+		fExpandCollapseButton = SWTFactory.createPushButton(buttonComp, Messages.BundleContainerTable_Btn_Text_ExpandAll, null);
 
 		fShowContentButton = SWTFactory.createCheckButton(comp, Messages.TargetLocationsGroup_1, null, false, 2);
 
@@ -278,8 +280,45 @@
 		fTreeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
 
 		createContextMenu(fTreeViewer.getTree());
+		fTreeViewer.getTree().addMouseListener(new MouseListener() {
+			@Override
+			public void mouseDoubleClick(MouseEvent e) {
+				setExpandCollapseState();
+			}
+			@Override
+			public void mouseDown(MouseEvent e) {
+			}
+			@Override
+			public void mouseUp(MouseEvent e) {
+				setExpandCollapseState();
+			}
+
+		});
+		fTreeViewer.getTree().addKeyListener(new KeyListener() {
+
+			@Override
+			public void keyPressed(KeyEvent e) {
+
+			}
+
+			@Override
+			public void keyReleased(KeyEvent e) {
+				setExpandCollapseState();
+			}
+
+		});
 	}
 
+	private void setExpandCollapseState() {
+		if (fTreeViewer == null)
+			return;
+		if (fTreeViewer.getVisibleExpandedElements().length == 0) {
+			fExpandCollapseButton.setText(Messages.BundleContainerTable_Btn_Text_ExpandAll);
+		} else {
+			fExpandCollapseButton.setText(Messages.BundleContainerTable_Btn_Text_CollapseAll);
+		}
+
+	}
 	private void createContextMenu(Tree tree) {
 		fCopySelectionAction = new CopyTreeSelectionAction(tree);
 
@@ -319,11 +358,17 @@
 		fReloadButton.setEnabled(true);
 		SWTFactory.setButtonDimensionHint(fReloadButton);
 
+		fExpandCollapseButton.addSelectionListener(widgetSelectedAdapter(e -> toggleCollapse()));
+		fExpandCollapseButton.setLayoutData(new GridData());
+		fExpandCollapseButton.setEnabled(false);
+		SWTFactory.setButtonDimensionHint(fExpandCollapseButton);
+
 		fShowContentButton.addSelectionListener(widgetSelectedAdapter(e -> {
 			((TargetLocationContentProvider) fTreeViewer.getContentProvider())
 					.setShowLocationContent(fShowContentButton.getSelection());
 			fTreeViewer.refresh();
 			fTreeViewer.expandAll();
+			fExpandCollapseButton.setText(Messages.BundleContainerTable_Btn_Text_CollapseAll);
 		}));
 		fShowContentButton.setLayoutData(new GridData());
 		SWTFactory.setButtonDimensionHint(fShowContentButton);
@@ -338,7 +383,10 @@
 	 */
 	public void setInput(ITargetDefinition target) {
 		fTarget = target;
+		boolean isCollapsed = fTreeViewer.getVisibleExpandedElements().length == 0;
 		fTreeViewer.setInput(fTarget);
+		if (isCollapsed)
+			fTreeViewer.collapseAll();
 		updateButtons();
 	}
 
@@ -461,6 +509,9 @@
 			fRemoveButton.setData(BUTTON_STATE, DeleteButtonState.NONE);
 			fUpdateButton.setEnabled(false);
 			fEditButton.setEnabled(false);
+			if(fTreeViewer !=null) {
+				setExpandCollapseState();
+			}
 			return;
 		}
 		boolean canRemove = false;
@@ -512,6 +563,19 @@
 
 	}
 
+	private void toggleCollapse() {
+		if (fTreeViewer == null)
+			return;
+		if (fTreeViewer.getVisibleExpandedElements().length == 0) {
+			fTreeViewer.expandAll();
+			fExpandCollapseButton.setText(Messages.BundleContainerTable_Btn_Text_CollapseAll);
+		} else {
+			fTreeViewer.collapseAll();
+			fExpandCollapseButton.setText(Messages.BundleContainerTable_Btn_Text_ExpandAll);
+		}
+
+	}
+
 	/**
 	 * Informs the reporter for this table that something has changed and is
 	 * dirty.
@@ -540,4 +604,9 @@
 		return status;
 	}
 
+	public void setExpandCollapseState(boolean b) {
+		if (fExpandCollapseButton != null)
+			fExpandCollapseButton.setEnabled(b);
+	}
+
 }
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/messages.properties b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/messages.properties
index 7a26c3b..c810c07 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/messages.properties
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/messages.properties
@@ -54,6 +54,8 @@
 BundleContainerTable_Btn_Text_Remove=&Remove
 BundleContainerTable_Btn_Text_Update=&Update
 BundleContainerTable_Btn_Text_Reload=Re&load
+BundleContainerTable_Btn_Text_ExpandAll=Ex&pand All
+BundleContainerTable_Btn_Text_CollapseAll=C&ollapse All
 
 BundleContainerTable_8=Unspecified Site
 # Styled label section describing number of plug-ins in the location
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/util/FileValidator.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/util/FileValidator.java
index 7f0547b..253a2d7 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/util/FileValidator.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/util/FileValidator.java
@@ -16,7 +16,6 @@
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
-import org.eclipse.pde.internal.ui.PDEPlugin;
 import org.eclipse.ui.dialogs.ISelectionStatusValidator;
 
 public class FileValidator implements ISelectionStatusValidator {
@@ -24,11 +23,9 @@
 	@Override
 	public IStatus validate(Object[] selection) {
 		if (selection.length > 0 && selection[0] instanceof IFile) {
-			return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", //$NON-NLS-1$
-					null);
+			return Status.OK_STATUS;
 		}
-		return new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, "", //$NON-NLS-1$
-				null);
+		return Status.error(""); //$NON-NLS-1$
 	}
 
 }
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/views/imagebrowser/repositories/AbstractRepository.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/views/imagebrowser/repositories/AbstractRepository.java
index ed6c44b..0ddcb8c 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/views/imagebrowser/repositories/AbstractRepository.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/views/imagebrowser/repositories/AbstractRepository.java
@@ -110,7 +110,7 @@
 						PDEPlugin.log(e);
 					} catch (SWTException e) {
 						// invalid image format
-						PDEPlugin.log(new Status(IStatus.ERROR, PDEPlugin.getPluginId(), NLS.bind(PDEUIMessages.AbstractRepository_ErrorLoadingImageFromJar, jarFile.getAbsolutePath(), entry.getName()), e));
+						PDEPlugin.log(Status.error(NLS.bind(PDEUIMessages.AbstractRepository_ErrorLoadingImageFromJar, jarFile.getAbsolutePath(), entry.getName()), e));
 					}
 				}
 			}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/PDEWizardNewFileCreationPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/PDEWizardNewFileCreationPage.java
index 395138d..aa0f450 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/PDEWizardNewFileCreationPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/PDEWizardNewFileCreationPage.java
@@ -17,7 +17,6 @@
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.pde.internal.ui.PDEPlugin;
 import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.ui.dialogs.WizardNewFileCreationPage;
 
@@ -55,7 +54,7 @@
 
 	@Override
 	protected IStatus validateLinkedResource() {
-		return new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", null); //$NON-NLS-1$
+		return Status.OK_STATUS;
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/RenameDialog.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/RenameDialog.java
index 549d727..cdf03ea 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/RenameDialog.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/RenameDialog.java
@@ -18,7 +18,8 @@
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.IInputValidator;
-import org.eclipse.pde.internal.ui.*;
+import org.eclipse.pde.internal.ui.IHelpContextIds;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
@@ -118,8 +119,7 @@
 		text.selectAll();
 		Button okButton = getButton(IDialogConstants.OK_ID);
 
-		status = new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", //$NON-NLS-1$
-				null);
+		status = Status.OK_STATUS;
 		updateStatus(status);
 		okButton.setEnabled(false);
 		return super.open();
@@ -130,7 +130,7 @@
 		if (fValidator != null) {
 			String message = fValidator.isValid(text);
 			if (message != null) {
-				status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, message, null);
+				status = Status.error(message);
 				updateStatus(status);
 				okButton.setEnabled(false);
 				return;
@@ -138,14 +138,13 @@
 		}
 		for (int i = 0; i < oldNames.size(); i++) {
 			if ((isCaseSensitive && text.equals(oldNames.get(i))) || (!isCaseSensitive && text.equalsIgnoreCase(oldNames.get(i).toString()))) {
-				status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, PDEUIMessages.RenameDialog_validationError, null);
+				status = Status.error(PDEUIMessages.RenameDialog_validationError);
 				updateStatus(status);
 				okButton.setEnabled(false);
 				break;
 			}
 			okButton.setEnabled(true);
-			status = new Status(IStatus.OK, PDEPlugin.getPluginId(), IStatus.OK, "", //$NON-NLS-1$
-					null);
+			status = Status.OK_STATUS;
 			updateStatus(status);
 		}
 	}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/exports/FeatureOptionsTab.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/exports/FeatureOptionsTab.java
index a227c9c..ffb63b8 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/exports/FeatureOptionsTab.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/exports/FeatureOptionsTab.java
@@ -90,7 +90,7 @@
 					}
 				}
 			}
-			return new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, PDEUIMessages.FeatureOptionsTab_0);
+			return Status.error(PDEUIMessages.FeatureOptionsTab_0);
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/extension/PointSelectionPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/extension/PointSelectionPage.java
index 5161b9a..b5520e1 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/extension/PointSelectionPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/extension/PointSelectionPage.java
@@ -587,7 +587,7 @@
 			public IBasePluginWizard createWizard() throws CoreException {
 				IExtensionWizard wizard = createWizard(wizardElement);
 				if (wizard == null)
-					throw new CoreException(new Status(IStatus.ERROR, wizardElement.getConfigurationElement().getNamespaceIdentifier(), PDEUIMessages.PointSelectionPage_cannotFindTemplate));
+					throw new CoreException(Status.error(PDEUIMessages.PointSelectionPage_cannotFindTemplate));
 				wizard.init(fProject, fModel);
 				return wizard;
 			}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/feature/AbstractFeatureSpecPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/feature/AbstractFeatureSpecPage.java
index 5441bcf..bf379a5 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/feature/AbstractFeatureSpecPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/feature/AbstractFeatureSpecPage.java
@@ -14,7 +14,6 @@
 
 package org.eclipse.pde.internal.ui.wizards.feature;
 
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.dialogs.Dialog;
 import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.pde.internal.core.ifeature.IFeatureModel;
@@ -141,7 +140,7 @@
 
 	protected String verifyVersion() {
 		String value = fFeatureVersionText.getText();
-		if (VersionUtil.validateVersion(value).getSeverity() != IStatus.OK)
+		if (!VersionUtil.validateVersion(value).isOK())
 			return PDEUIMessages.NewFeatureWizard_SpecPage_versionFormat;
 		return null;
 	}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/feature/CreateFeatureProjectFromLaunchOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/feature/CreateFeatureProjectFromLaunchOperation.java
index ba42647..bab706f 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/feature/CreateFeatureProjectFromLaunchOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/feature/CreateFeatureProjectFromLaunchOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2015 IBM Corporation and others.
+ * Copyright (c) 2007, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -15,10 +15,8 @@
 
 package org.eclipse.pde.internal.ui.wizards.feature;
 
-import org.eclipse.pde.launching.IPDELauncherConstants;
-
-import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
-
+import java.util.Collections;
+import java.util.Set;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
@@ -28,6 +26,8 @@
 import org.eclipse.pde.core.plugin.IPluginModelBase;
 import org.eclipse.pde.internal.core.feature.WorkspaceFeatureModel;
 import org.eclipse.pde.internal.core.ifeature.IFeature;
+import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
+import org.eclipse.pde.launching.IPDELauncherConstants;
 import org.eclipse.pde.ui.launcher.EclipseLaunchShortcut;
 import org.eclipse.swt.widgets.Shell;
 
@@ -47,22 +47,20 @@
 	}
 
 	private IPluginBase[] getPlugins() {
-		IPluginModelBase[] models = null;
+		Set<IPluginModelBase> models = Collections.emptySet();
 		try {
 			ILaunchConfigurationType type = fLaunchConfig.getType();
 			String id = type.getIdentifier();
 			// if it is an Eclipse launch
-			if (id.equals(EclipseLaunchShortcut.CONFIGURATION_TYPE))
-				models = BundleLauncherHelper.getMergedBundles(fLaunchConfig, false);
-			// else if it is an OSGi launch
-			else if (id.equals(IPDELauncherConstants.OSGI_CONFIGURATION_TYPE))
-				models = BundleLauncherHelper.getMergedBundles(fLaunchConfig, true);
+			if (id.equals(EclipseLaunchShortcut.CONFIGURATION_TYPE)) {
+				models = BundleLauncherHelper.getMergedBundleMap(fLaunchConfig, false).keySet();
+			} else if (id.equals(IPDELauncherConstants.OSGI_CONFIGURATION_TYPE)) {
+				// else if it is an OSGi launch
+				models = BundleLauncherHelper.getMergedBundleMap(fLaunchConfig, true).keySet();
+			}
 		} catch (CoreException e) {
 		}
-		IPluginBase[] result = new IPluginBase[models == null ? 0 : models.length];
-		for (int i = 0; i < result.length; i++)
-			result[i] = models[i].getPluginBase(true);
-		return result;
+		return models.stream().map(IPluginModelBase::getPluginBase).toArray(IPluginBase[]::new);
 	}
 
 }
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/FeatureImportOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/FeatureImportOperation.java
index e986bb5..325adb4 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/FeatureImportOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/FeatureImportOperation.java
@@ -170,7 +170,7 @@
 			if (th instanceof CoreException) {
 				throw (CoreException) th;
 			}
-			IStatus status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, e.getMessage(), e);
+			IStatus status = Status.error(e.getMessage(), e);
 			throw new CoreException(status);
 		} catch (InterruptedException e) {
 			throw new OperationCanceledException(e.getMessage());
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/FeatureImportWizardPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/FeatureImportWizardPage.java
index 62acbc1..878d1da 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/FeatureImportWizardPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/FeatureImportWizardPage.java
@@ -496,11 +496,11 @@
 
 			model.load(stream, false);
 			if (!model.isValid()) {
-				status = new Status(IStatus.WARNING, IPDEUIConstants.PLUGIN_ID, IStatus.OK, NLS.bind(PDEUIMessages.FeatureImportWizardPage_importHasInvalid, dir), null);
+				status = Status.warning(NLS.bind(PDEUIMessages.FeatureImportWizardPage_importHasInvalid, dir));
 			}
 		} catch (Exception e) {
 			// Errors in the file
-			status = new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, IStatus.OK, e.getMessage(), e);
+			status = Status.error(e.getMessage(), e);
 		}
 		if (status == null)
 			result.add(model);
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportHelper.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportHelper.java
index 863d844..b62a3f4 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportHelper.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportHelper.java
@@ -20,7 +20,6 @@
 import java.util.zip.ZipFile;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.runtime.*;
-import org.eclipse.pde.internal.ui.PDEPlugin;
 import org.eclipse.ui.dialogs.IOverwriteQuery;
 import org.eclipse.ui.wizards.datatransfer.*;
 
@@ -50,7 +49,7 @@
 			}
 			op.run(monitor);
 		} catch (InvocationTargetException e) {
-			IStatus status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, e.getMessage(), e);
+			IStatus status = Status.error(e.getMessage(), e);
 			throw new CoreException(status);
 		} catch (InterruptedException e) {
 			throw new OperationCanceledException(e.getMessage());
@@ -78,7 +77,7 @@
 
 			importContent(provider.getRoot(), dstPath, provider, null, monitor);
 		} catch (IOException e) {
-			IStatus status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, e.getMessage(), e);
+			IStatus status = Status.error(e.getMessage(), e);
 			throw new CoreException(status);
 		}
 	}
@@ -103,7 +102,7 @@
 			}
 			importContent(provider.getRoot(), dstPath, provider, collected, monitor);
 		} catch (IOException e) {
-			IStatus status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, e.getMessage(), e);
+			IStatus status = Status.error(e.getMessage(), e);
 			throw new CoreException(status);
 		}
 	}
@@ -129,7 +128,7 @@
 			}
 			importContent(provider.getRoot(), dstPath, provider, collected, monitor);
 		} catch (IOException e) {
-			IStatus status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, e.getMessage(), e);
+			IStatus status = Status.error(e.getMessage(), e);
 			throw new CoreException(status);
 		}
 	}
@@ -182,7 +181,7 @@
 				dstFile.create(fstream, true, monitor);
 			}
 		} catch (IOException e) {
-			IStatus status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.OK, e.getMessage(), e);
+			IStatus status = Status.error(e.getMessage(), e);
 			throw new CoreException(status);
 		}
 	}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportOperation.java
index 699d499..8005fb0 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportOperation.java
@@ -372,15 +372,15 @@
 					if (RepositoryProvider.isShared(project))
 						RepositoryProvider.unmap(project);
 					if (!safeDeleteCheck(project, monitor)) {
-						status.add(new Status(IStatus.ERROR, PDEPlugin.getPluginId(), NLS.bind(
-								PDEUIMessages.PluginImportOperation_could_not_delete_project, project.getName())));
+						status.add(Status.error(NLS.bind(PDEUIMessages.PluginImportOperation_could_not_delete_project,
+								project.getName())));
 					}
 					boolean deleteContent = project.getWorkspace().getRoot().getLocation()
 							.equals(project.getLocation().removeLastSegments(1));
 					deleteProject(project, deleteContent, monitor);
 
 				} catch (CoreException ex) {
-					status.add(new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.OK,
+					status.add(Status.error(
 							NLS.bind(PDEUIMessages.PluginImportOperation_could_not_delete_project, project.getName()),
 							ex));
 				}
@@ -644,8 +644,7 @@
 			subMonitor.worked(1);
 
 		} catch (IOException e) {
-			IStatus status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, e.getMessage(), e);
-			throw new CoreException(status);
+			throw new CoreException(Status.error(e.getMessage(), e));
 		}
 
 	}
@@ -1087,8 +1086,7 @@
 						PluginImportHelper.collectNonJavaNonBuildFiles(provider, provider.getRoot(), collected);
 						PluginImportHelper.importContent(provider.getRoot(), project.getFullPath(), provider, collected, monitor);
 					} catch (IOException e) {
-						IStatus status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, e.getMessage(), e);
-						throw new CoreException(status);
+						throw new CoreException(Status.error(e.getMessage(), e));
 					} finally {
 						if (zip != null) {
 							try {
@@ -1124,8 +1122,7 @@
 				PluginImportHelper.collectRequiredBundleFiles(provider, provider.getRoot(), collected);
 				PluginImportHelper.importContent(provider.getRoot(), project.getFullPath(), provider, collected, monitor);
 			} catch (IOException e) {
-				IStatus status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, e.getMessage(), e);
-				throw new CoreException(status);
+				throw new CoreException(Status.error(e.getMessage(), e));
 			} finally {
 				if (zip != null) {
 					try {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportWizard.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportWizard.java
index 64d9655..3514189 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportWizard.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/imports/PluginImportWizard.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2000, 2015 IBM Corporation and others.
+ *  Copyright (c) 2000, 2021 IBM Corporation and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -14,7 +14,8 @@
  *******************************************************************************/
 package org.eclipse.pde.internal.ui.wizards.imports;
 
-import java.util.*;
+import java.util.HashSet;
+import java.util.Map;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.debug.core.*;
@@ -140,9 +141,8 @@
 				if (configuration == null)
 					continue;
 				try {
-					Map<?, ?> workspaceBundleMap = BundleLauncherHelper.getWorkspaceBundleMap(configuration);
-					for (Object key : workspaceBundleMap.keySet()) {
-						IPluginModelBase bm = (IPluginModelBase) key;
+					var workspaceBundles = BundleLauncherHelper.getWorkspaceBundleMap(configuration).keySet();
+					for (IPluginModelBase bm : workspaceBundles) {
 						BundleDescription description = bm.getBundleDescription();
 						if (description != null) {
 							if (imported.contains(description.getSymbolicName())) {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/ContentPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/ContentPage.java
index c56ca51..fc9cb37 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/ContentPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/ContentPage.java
@@ -16,8 +16,6 @@
 package org.eclipse.pde.internal.ui.wizards.plugin;
 
 import java.text.MessageFormat;
-
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.dialogs.IDialogSettings;
 import org.eclipse.jface.wizard.IWizardPage;
 import org.eclipse.jface.wizard.WizardPage;
@@ -143,7 +141,7 @@
 	}
 
 	protected boolean isVersionValid(String version) {
-		return VersionUtil.validateVersion(version).getSeverity() == IStatus.OK;
+		return VersionUtil.validateVersion(version).isOK();
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/FragmentContentPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/FragmentContentPage.java
index 1d187fc..0ae0c98 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/FragmentContentPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/FragmentContentPage.java
@@ -331,7 +331,7 @@
 				} else {
 					if (fNewVersion) {
 						IStatus status = fVersionPart.validateFullVersionRangeText(false);
-						if (status.getSeverity() != IStatus.OK) {
+						if (!status.isOK()) {
 							errorMessage = status.getMessage();
 						}
 					} else {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewLibraryPluginCreationOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewLibraryPluginCreationOperation.java
index 32239ba..1444313 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewLibraryPluginCreationOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewLibraryPluginCreationOperation.java
@@ -40,7 +40,8 @@
 import org.eclipse.pde.internal.core.natures.PDE;
 import org.eclipse.pde.internal.core.plugin.WorkspacePluginModelBase;
 import org.eclipse.pde.internal.core.project.PDEProject;
-import org.eclipse.pde.internal.ui.*;
+import org.eclipse.pde.internal.ui.PDEPlugin;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.pde.internal.ui.search.dependencies.AddNewBinaryDependenciesOperation;
 import org.eclipse.pde.internal.ui.wizards.IProjectProvider;
 import org.eclipse.pde.ui.IFieldData;
@@ -349,8 +350,8 @@
 					pathString -> IOverwriteQuery.ALL);
 			op.run(monitor);
 		} catch (IOException e) {
-			throw new CoreException(new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, IStatus.OK,
-					NLS.bind(PDEUIMessages.NewProjectCreationOperation_errorImportingJar, jar), e));
+			throw new CoreException(
+					Status.error(NLS.bind(PDEUIMessages.NewProjectCreationOperation_errorImportingJar, jar), e));
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewLibraryPluginCreationPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewLibraryPluginCreationPage.java
index 50546a3..45723e6 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewLibraryPluginCreationPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewLibraryPluginCreationPage.java
@@ -19,7 +19,6 @@
 import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;
 
 import java.util.TreeSet;
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jdt.launching.IVMInstall;
 import org.eclipse.jdt.launching.JavaRuntime;
 import org.eclipse.jdt.launching.environments.IExecutionEnvironment;
@@ -259,7 +258,7 @@
 	}
 
 	protected boolean isVersionValid(String version) {
-		return VersionUtil.validateVersion(version).getSeverity() == IStatus.OK;
+		return VersionUtil.validateVersion(version).isOK();
 	}
 
 	public void updateData() {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewProjectCreationOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewProjectCreationOperation.java
index 3c239984..27e0fe6 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewProjectCreationOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewProjectCreationOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2020 IBM Corporation and others.
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -20,7 +20,6 @@
 
 import java.lang.reflect.InvocationTargetException;
 import java.util.*;
-import java.util.regex.Pattern;
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
@@ -485,10 +484,7 @@
 		}
 		IClasspathEntry[] entries = new IClasspathEntry[1];
 		IPath path = project.getProject().getFullPath().append(data.getSourceFolderName());
-		String testPluginPattern = PDECore.getDefault().getPreferencesManager()
-				.getString(ICoreConstants.TEST_PLUGIN_PATTERN);
-		boolean isTestPlugin = testPluginPattern != null && testPluginPattern.length() > 0
-				&& Pattern.compile(testPluginPattern).matcher(project.getProject().getName()).find();
+		boolean isTestPlugin = ClasspathComputer.hasTestPluginName(project.getProject());
 		if (isTestPlugin) {
 			IClasspathAttribute testAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true"); //$NON-NLS-1$
 			entries[0] = JavaCore.newSourceEntry(path, null, null, null, new IClasspathAttribute[] { testAttribute });
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/BaseManifestOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/BaseManifestOperation.java
index 062a8e8..af31718 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/BaseManifestOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/BaseManifestOperation.java
@@ -64,8 +64,8 @@
 		if (plugin instanceof IBundlePluginModel) {
 			IFile file = (IFile) plugin.getUnderlyingResource();
 			IStatus status = PDEPlugin.getWorkspace().validateEdit(new IFile[] {file}, fShell);
-			if (status.getSeverity() != IStatus.OK)
-				throw new CoreException(new Status(IStatus.ERROR, "org.eclipse.pde.ui", IStatus.ERROR, NLS.bind(PDEUIMessages.ProductDefinitionOperation_readOnly, fPluginId), null)); //$NON-NLS-1$
+			if (!status.isOK())
+				throw new CoreException(Status.error(NLS.bind(PDEUIMessages.ProductDefinitionOperation_readOnly, fPluginId), null));
 
 			ModelModification mod = new ModelModification(file) {
 				@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductDefinitionOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductDefinitionOperation.java
index 5e0cfa6..3600003 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductDefinitionOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductDefinitionOperation.java
@@ -460,8 +460,8 @@
 
 	private void modifyExistingFile(IFile file, IProgressMonitor monitor) throws CoreException {
 		IStatus status = PDEPlugin.getWorkspace().validateEdit(new IFile[] {file}, getShell());
-		if (status.getSeverity() != IStatus.OK)
-			throw new CoreException(new Status(IStatus.ERROR, "org.eclipse.pde.ui", IStatus.ERROR, NLS.bind(PDEUIMessages.ProductDefinitionOperation_readOnly, fPluginId), null)); //$NON-NLS-1$
+		if (!status.isOK())
+			throw new CoreException(Status.error(NLS.bind(PDEUIMessages.ProductDefinitionOperation_readOnly, fPluginId)));
 
 		ModelModification mod = new ModelModification(file) {
 			@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromConfigOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromConfigOperation.java
index 0226657..3d798fe 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromConfigOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromConfigOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2015 IBM Corporation and others.
+ * Copyright (c) 2005, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -74,9 +74,7 @@
 			}
 
 			// fetch the plug-ins models
-			Set<String> set = new HashSet<>();
-			Map<IPluginModelBase, String> map = BundleLauncherHelper.getWorkspaceBundleMap(fLaunchConfiguration, set);
-			map.putAll(BundleLauncherHelper.getTargetBundleMap(fLaunchConfiguration, set));
+			Map<IPluginModelBase, String> map = BundleLauncherHelper.getAllSelectedPluginBundles(fLaunchConfiguration);
 
 			addPlugins(factory, product, map);
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromExtensionOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromExtensionOperation.java
index d075451..041df04 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromExtensionOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromExtensionOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2005, 2021 IBM Corporation and others.
+ *  Copyright (c) 2005, 2022 IBM Corporation and others.
  *
  *  This program and the accompanying materials
  *  are made available under the terms of the Eclipse Public License 2.0
@@ -67,7 +67,8 @@
 				}
 			}
 		}
-		Set<BundleDescription> bundles = DependencyManager.findRequirementsClosure(plugins, false);
+		Set<BundleDescription> bundles = DependencyManager.findRequirementsClosure(plugins,
+				DependencyManager.Options.INCLUDE_NON_TEST_FRAGMENTS);
 		return bundles.stream().map(BundleDescription::getSymbolicName).toArray(String[]::new);
 	}
 
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductIntroOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductIntroOperation.java
index e8a2344..44dcebc 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductIntroOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductIntroOperation.java
@@ -141,8 +141,8 @@
 
 	private void modifyExistingFile(IFile file, IProgressMonitor monitor) throws CoreException {
 		IStatus status = PDEPlugin.getWorkspace().validateEdit(new IFile[] {file}, fShell);
-		if (status.getSeverity() != IStatus.OK)
-			throw new CoreException(new Status(IStatus.ERROR, "org.eclipse.pde.ui", IStatus.ERROR, NLS.bind(PDEUIMessages.ProductDefinitionOperation_readOnly, fPluginId), null)); //$NON-NLS-1$
+		if (!status.isOK())
+			throw new CoreException(Status.error(NLS.bind(PDEUIMessages.ProductDefinitionOperation_readOnly, fPluginId)));
 
 		ModelModification mod = new ModelModification(file) {
 			@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/SynchronizationOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/SynchronizationOperation.java
index fdfbd90..d8a80e7 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/SynchronizationOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/SynchronizationOperation.java
@@ -60,8 +60,7 @@
 	}
 
 	private CoreException createCoreException(String message) {
-		IStatus status = new Status(IStatus.ERROR, "org.eclipse.pde.ui", IStatus.ERROR, message, null); //$NON-NLS-1$
-		return new CoreException(status);
+		return new CoreException(Status.error(message));
 	}
 
 }
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/UpdateSplashProgressOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/UpdateSplashProgressOperation.java
index 9b1c23f..079082f 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/UpdateSplashProgressOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/UpdateSplashProgressOperation.java
@@ -30,7 +30,6 @@
 import org.eclipse.pde.internal.core.text.build.BuildModel;
 import org.eclipse.pde.internal.core.text.build.PropertiesTextChangeListener;
 import org.eclipse.pde.internal.core.util.PDETextHelper;
-import org.eclipse.pde.internal.ui.IPDEUIConstants;
 import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.text.edits.*;
 
@@ -210,16 +209,6 @@
 		updatePluginCustomizationFile((IFile) resource, monitor);
 	}
 
-	private CoreException createCoreException(String message, Throwable exception) {
-		IStatus status = Status.error(message, exception);
-		return new CoreException(status);
-	}
-
-	private CoreException createCoreException(String message) {
-		IStatus status = new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, message);
-		return new CoreException(status);
-	}
-
 	private ITextFileBufferManager getTextFileBufferManager() throws CoreException {
 		if (fTextFileBufferManager == null) {
 			// Get the text file buffer manager
@@ -227,7 +216,8 @@
 		}
 		// Ensure manager is defined
 		if (fTextFileBufferManager == null) {
-			throw createCoreException(PDEUIMessages.UpdateSplashProgressAction_msgErrorTextFileBufferManager);
+			throw new CoreException(
+					Status.error(PDEUIMessages.UpdateSplashProgressAction_msgErrorTextFileBufferManager));
 		}
 		return fTextFileBufferManager;
 	}
@@ -239,7 +229,7 @@
 		fTextFileBuffer = getTextFileBufferManager().getTextFileBuffer(path, kind);
 		// Ensure buffer is defined
 		if (fTextFileBuffer == null) {
-			throw createCoreException(PDEUIMessages.UpdateSplashProgressAction_msgErrorTextFileBuffer);
+			throw new CoreException(Status.error(PDEUIMessages.UpdateSplashProgressAction_msgErrorTextFileBuffer));
 		}
 		return fTextFileBuffer;
 	}
@@ -287,7 +277,8 @@
 			// Save plugin customization file changes
 			savePluginCustomFileChanges(pluginCustomModel, subMonitor.split(1));
 		} catch (MalformedTreeException | BadLocationException e) {
-			throw createCoreException(PDEUIMessages.UpdateSplashProgressAction_msgErrorCustomFileSaveFailed, e);
+			throw new CoreException(
+					Status.error(PDEUIMessages.UpdateSplashProgressAction_msgErrorCustomFileSaveFailed, e));
 		} finally {
 			// Disconnect from the text file buffer manager
 			getTextFileBufferManager().disconnect(path, kind, subMonitor.split(1));
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/target/TargetDefinitionContentPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/target/TargetDefinitionContentPage.java
index f471c06..1f5ea9f 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/target/TargetDefinitionContentPage.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/target/TargetDefinitionContentPage.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2009, 2016 IBM Corporation and others.
+ * Copyright (c) 2009, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -21,6 +21,8 @@
 import java.util.*;
 import java.util.List;
 import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
 import org.eclipse.core.runtime.preferences.InstanceScope;
 import org.eclipse.debug.ui.StringVariableSelectionDialog;
@@ -233,7 +235,7 @@
 		super.targetChanged(definition);
 		if (definition != null) {
 			// When  If the page isn't open yet, try running a UI job so the dialog has time to finish opening
-			new UIJob(PDEUIMessages.TargetDefinitionContentPage_0) {
+			UIJob resolveJob = new UIJob(PDEUIMessages.TargetDefinitionContentPage_0) {
 				@Override
 				public IStatus runInUIThread(IProgressMonitor monitor) {
 					ITargetDefinition definition = getTargetDefinition();
@@ -261,7 +263,24 @@
 					}
 					return Status.OK_STATUS;
 				}
-			}.schedule();
+			};
+			resolveJob.schedule();
+			resolveJob.addJobChangeListener(new JobChangeAdapter() {
+				@Override
+				public void done(IJobChangeEvent event) {
+					UIJob job = new UIJob("") { //$NON-NLS-1$
+						@Override
+						public IStatus runInUIThread(IProgressMonitor monitor) {
+							if (fLocationTree != null) {
+								fLocationTree.setExpandCollapseState(true);
+							}
+							return Status.OK_STATUS;
+						}
+					};
+					job.setSystem(true);
+					job.schedule();
+				}
+			});
 			String name = definition.getName();
 			if (name == null) {
 				name = EMPTY_STRING;
@@ -667,7 +686,7 @@
 		List<IPluginModelBase> targetBundles = new ArrayList<>();
 
 		if (allTargetBundles == null || allTargetBundles.length == 0) {
-			throw new CoreException(new Status(IStatus.WARNING, PDEPlugin.getPluginId(), PDEUIMessages.ImplicitDependenciesSection_0));
+			throw new CoreException(Status.warning(PDEUIMessages.ImplicitDependenciesSection_0));
 		}
 		for (TargetBundle targetBundle : allTargetBundles) {
 			BundleInfo bundleInfo = targetBundle.getBundleInfo();
@@ -700,7 +719,7 @@
 		List<BundleInfo> targetBundles = new ArrayList<>();
 		TargetBundle[] allTargetBundles = getTargetDefinition().getAllBundles();
 		if (allTargetBundles == null || allTargetBundles.length == 0) {
-			throw new CoreException(new Status(IStatus.WARNING, PDEPlugin.getPluginId(), PDEUIMessages.ImplicitDependenciesSection_0));
+			throw new CoreException(Status.warning(PDEUIMessages.ImplicitDependenciesSection_0));
 		}
 
 		for (TargetBundle targetBundle : allTargetBundles) {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/UpdateClasspathJob.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/UpdateClasspathJob.java
index 7c8476c..c6d6d9f 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/UpdateClasspathJob.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/tools/UpdateClasspathJob.java
@@ -87,9 +87,9 @@
 			String title = PDEUIMessages.UpdateClasspathJob_error_title;
 			String message = PDEUIMessages.UpdateClasspathJob_error_message;
 			PDEPlugin.logException(e, title, message);
-			return new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, IStatus.OK, message, e);
+			return Status.error(message, e);
 		}
-		return new Status(IStatus.OK, IPDEUIConstants.PLUGIN_ID, IStatus.OK, "", null); //$NON-NLS-1$
+		return Status.OK_STATUS;
 	}
 
 }
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/ui/launcher/EclipseApplicationLaunchConfiguration.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/ui/launcher/EclipseApplicationLaunchConfiguration.java
index e40c367..9b2fa97 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/ui/launcher/EclipseApplicationLaunchConfiguration.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/ui/launcher/EclipseApplicationLaunchConfiguration.java
@@ -60,7 +60,7 @@
 
 	@Override
 	public String[] getProgramArguments(ILaunchConfiguration configuration) throws CoreException {
-		throw new CoreException(LauncherUtils.createErrorStatus(PDEMessages.PDE_updateManagerNotSupported));
+		throw new CoreException(Status.error((String) PDEMessages.PDE_updateManagerNotSupported));
 	}
 
 	@Override
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/ui/launcher/EquinoxLaunchConfiguration.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/ui/launcher/EquinoxLaunchConfiguration.java
index e10cf13..cf36dde 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/ui/launcher/EquinoxLaunchConfiguration.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/ui/launcher/EquinoxLaunchConfiguration.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2017 IBM Corporation and others.
+ * Copyright (c) 2005, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -18,6 +18,7 @@
 import java.net.URL;
 import java.util.*;
 import java.util.Map.Entry;
+import java.util.stream.Collectors;
 import org.eclipse.core.runtime.*;
 import org.eclipse.debug.core.ILaunch;
 import org.eclipse.debug.core.ILaunchConfiguration;
@@ -47,8 +48,8 @@
 public class EquinoxLaunchConfiguration extends AbstractPDELaunchConfiguration {
 
 	// used to generate the dev classpath entries
-	// key is bundle ID, value is a model
-	protected Map<String, IPluginModelBase> fAllBundles;
+	// key is bundle ID, value is a List of models
+	protected Map<String, List<IPluginModelBase>> fAllBundles;
 
 	// key is a model, value is startLevel:autoStart
 	private Map<IPluginModelBase, String> fModels;
@@ -86,7 +87,7 @@
 				properties.setProperty("org.eclipse.equinox.simpleconfigurator.configUrl", bundlesTxt.toString()); //$NON-NLS-1$
 			}
 			StringBuilder buffer = new StringBuilder();
-			IPluginModelBase model = fAllBundles.get(IPDEBuildConstants.BUNDLE_SIMPLE_CONFIGURATOR);
+			IPluginModelBase model = LaunchConfigurationHelper.getLatestModel(IPDEBuildConstants.BUNDLE_SIMPLE_CONFIGURATOR, fAllBundles);
 			buffer.append(LaunchConfigurationHelper.getBundleURL(model, true));
 			appendStartData(buffer, fModels.get(model), autostart);
 			bundles = buffer.toString();
@@ -154,22 +155,18 @@
 	@Override
 	protected void preLaunchCheck(ILaunchConfiguration configuration, ILaunch launch, IProgressMonitor monitor) throws CoreException {
 		fModels = BundleLauncherHelper.getMergedBundleMap(configuration, true);
-		fAllBundles = new HashMap<>(fModels.size());
-		Iterator<IPluginModelBase> iter = fModels.keySet().iterator();
-		while (iter.hasNext()) {
-			IPluginModelBase model = iter.next();
-			fAllBundles.put(model.getPluginBase().getId(), model);
-		}
+		fAllBundles = fModels.keySet().stream().collect(Collectors.groupingBy(m -> m.getPluginBase().getId(),
+				HashMap::new, Collectors.toCollection(ArrayList::new)));
 
 		if (!fAllBundles.containsKey(IPDEBuildConstants.BUNDLE_OSGI)) {
 			// implicitly add it
 			IPluginModelBase model = PluginRegistry.findModel(IPDEBuildConstants.BUNDLE_OSGI);
 			if (model != null) {
 				fModels.put(model, "default:default"); //$NON-NLS-1$
-				fAllBundles.put(IPDEBuildConstants.BUNDLE_OSGI, model);
+				fAllBundles.computeIfAbsent(IPDEBuildConstants.BUNDLE_OSGI, i -> new ArrayList<>()).add(model);
 			} else {
 				String message = PDEMessages.EquinoxLaunchConfiguration_oldTarget;
-				throw new CoreException(LauncherUtils.createErrorStatus(message));
+				throw new CoreException(Status.error((String) message));
 			}
 		}
 		super.preLaunchCheck(configuration, launch, monitor);
diff --git a/ui/org.eclipse.pde.ui/src_samples/org/eclipse/pde/internal/ui/samples/SampleOperation.java b/ui/org.eclipse.pde.ui/src_samples/org/eclipse/pde/internal/ui/samples/SampleOperation.java
index 7daf74f..9cd044f 100644
--- a/ui/org.eclipse.pde.ui/src_samples/org/eclipse/pde/internal/ui/samples/SampleOperation.java
+++ b/ui/org.eclipse.pde.ui/src_samples/org/eclipse/pde/internal/ui/samples/SampleOperation.java
@@ -23,7 +23,8 @@
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.jface.operation.IRunnableWithProgress;
-import org.eclipse.pde.internal.ui.*;
+import org.eclipse.pde.internal.ui.PDEPlugin;
+import org.eclipse.pde.internal.ui.PDEUIMessages;
 import org.eclipse.ui.dialogs.IOverwriteQuery;
 import org.eclipse.ui.wizards.datatransfer.ImportOperation;
 import org.eclipse.ui.wizards.datatransfer.ZipFileStructureProvider;
@@ -99,9 +100,7 @@
 	}
 
 	private void throwCoreException(InvocationTargetException e) throws CoreException {
-		Throwable t = e.getCause();
-		Status status = new Status(IStatus.ERROR, IPDEUIConstants.PLUGIN_ID, IStatus.OK, e.getMessage(), t);
-		throw new CoreException(status);
+		throw new CoreException(Status.error(e.getMessage(), e.getCause()));
 	}
 
 	private IFile importProject(String name, IConfigurationElement config, IProgressMonitor monitor) throws CoreException, InvocationTargetException, InterruptedException {
@@ -193,9 +192,7 @@
 			URL starterURL = FileLocator.resolve(bundle.getEntry(pluginRelativePath));
 			return new ZipFile(FileLocator.toFileURL(starterURL).getFile());
 		} catch (IOException e) {
-			String message = pluginRelativePath + ": " + e.getMessage(); //$NON-NLS-1$
-			Status status = new Status(IStatus.ERROR, PDEPlugin.getPluginId(), IStatus.ERROR, message, e);
-			throw new CoreException(status);
+			throw new CoreException(Status.error(pluginRelativePath + ": " + e.getMessage(), e)); //$NON-NLS-1$
 		}
 	}
 
diff --git a/ui/org.eclipse.pde.unittest.junit/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.unittest.junit/META-INF/MANIFEST.MF
index d52f6ce..4e60909 100644
--- a/ui/org.eclipse.pde.unittest.junit/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.unittest.junit/META-INF/MANIFEST.MF
@@ -3,7 +3,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.pde.unittest.junit;singleton:=true
-Bundle-Version: 1.0.0.qualifier
+Bundle-Version: 1.0.100.qualifier
 Bundle-Activator: org.eclipse.pde.unittest.junit.JUnitPluginTestPlugin
 Bundle-ActivationPolicy: lazy
 Bundle-Vendor: %providerName
diff --git a/ui/org.eclipse.pde.unittest.junit/pom.xml b/ui/org.eclipse.pde.unittest.junit/pom.xml
index d02e473..a77f6b1 100644
--- a/ui/org.eclipse.pde.unittest.junit/pom.xml
+++ b/ui/org.eclipse.pde.unittest.junit/pom.xml
@@ -13,13 +13,12 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde.unittest.junit</artifactId>
-  <version>1.0.0-SNAPSHOT</version>
+  <version>1.0.100-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
   <properties>
       <skipAPIAnalysis>true</skipAPIAnalysis>
diff --git a/ui/org.eclipse.pde.unittest.junit/src/org/eclipse/pde/unittest/junit/JUnitPluginTestPlugin.java b/ui/org.eclipse.pde.unittest.junit/src/org/eclipse/pde/unittest/junit/JUnitPluginTestPlugin.java
index 570fe51..7c16212 100644
--- a/ui/org.eclipse.pde.unittest.junit/src/org/eclipse/pde/unittest/junit/JUnitPluginTestPlugin.java
+++ b/ui/org.eclipse.pde.unittest.junit/src/org/eclipse/pde/unittest/junit/JUnitPluginTestPlugin.java
@@ -61,7 +61,7 @@
 	}
 
 	public static void log(Throwable e) {
-		log(new Status(IStatus.ERROR, getPluginId(), IStatus.ERROR, "Error", e)); //$NON-NLS-1$
+		log(Status.error("Error", e)); //$NON-NLS-1$
 	}
 
 	public static void log(IStatus status) {
diff --git a/ui/org.eclipse.pde.unittest.junit/src/org/eclipse/pde/unittest/junit/launcher/JUnitPluginLaunchConfigurationDelegate.java b/ui/org.eclipse.pde.unittest.junit/src/org/eclipse/pde/unittest/junit/launcher/JUnitPluginLaunchConfigurationDelegate.java
index 61a06cc..807e90a 100644
--- a/ui/org.eclipse.pde.unittest.junit/src/org/eclipse/pde/unittest/junit/launcher/JUnitPluginLaunchConfigurationDelegate.java
+++ b/ui/org.eclipse.pde.unittest.junit/src/org/eclipse/pde/unittest/junit/launcher/JUnitPluginLaunchConfigurationDelegate.java
@@ -26,15 +26,14 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
-import org.osgi.framework.Version;
 
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.pde.core.plugin.IFragmentModel;
@@ -46,7 +45,6 @@
 import org.eclipse.pde.internal.core.PDECore;
 import org.eclipse.pde.internal.core.TargetPlatformHelper;
 import org.eclipse.pde.internal.core.util.CoreUtility;
-import org.eclipse.pde.internal.core.util.VersionUtil;
 import org.eclipse.pde.internal.launching.IPDEConstants;
 import org.eclipse.pde.internal.launching.launcher.BundleLauncherHelper;
 import org.eclipse.pde.internal.launching.launcher.EclipsePluginValidationOperation;
@@ -253,8 +251,7 @@
 							System.arraycopy(classpath, 0, classpath = new String[length + 1], 0, length);
 							classpath[length] = entryString;
 						} catch (IOException | URISyntaxException e) {
-							throw new CoreException(
-									new Status(IStatus.ERROR, JUnitPluginTestPlugin.PLUGIN_ID, IStatus.ERROR, "", e)); //$NON-NLS-1$
+							throw new CoreException(Status.error("", e)); //$NON-NLS-1$
 						}
 					}
 				}
@@ -362,12 +359,8 @@
 		fWorkspaceLocation = null;
 		fConfigDir = null;
 		fModels = BundleLauncherHelper.getMergedBundleMap(configuration, false);
-		fAllBundles = new LinkedHashMap<>(fModels.size());
-		Iterator<IPluginModelBase> iter = fModels.keySet().iterator();
-		while (iter.hasNext()) {
-			IPluginModelBase model = iter.next();
-			fAllBundles.put(model.getPluginBase().getId(), model);
-		}
+		fAllBundles = fModels.keySet().stream().collect(Collectors.groupingBy(m -> m.getPluginBase().getId(),
+				LinkedHashMap::new, Collectors.toCollection(ArrayList::new)));
 
 		// implicitly add the plug-ins required for JUnit testing if necessary
 		String[] requiredPlugins = getRequiredPlugins(configuration);
@@ -375,7 +368,7 @@
 			String id = requiredPlugin;
 			if (!fAllBundles.containsKey(id)) {
 				IPluginModelBase model = findRequiredPluginInTargetOrHost(id);
-				fAllBundles.put(id, model);
+				fAllBundles.computeIfAbsent(id, i -> new ArrayList<>()).add(model);
 				fModels.put(model, "default:default"); //$NON-NLS-1$
 			}
 		}
@@ -809,7 +802,7 @@
 			}
 			return file.getAbsolutePath();
 		} catch (IOException | JavaModelException e) {
-			throw new CoreException(new Status(IStatus.ERROR, JUnitPluginTestPlugin.PLUGIN_ID, IStatus.ERROR, "", e)); //$NON-NLS-1$
+			throw new CoreException(Status.error("", e)); //$NON-NLS-1$
 		}
 	}
 
@@ -850,7 +843,7 @@
 			}
 			return file.getAbsolutePath();
 		} catch (IOException e) {
-			throw new CoreException(new Status(IStatus.ERROR, JUnitPluginTestPlugin.PLUGIN_ID, IStatus.ERROR, "", e)); //$NON-NLS-1$
+			throw new CoreException(Status.error("", e)); //$NON-NLS-1$
 		}
 	}
 
@@ -989,7 +982,7 @@
 
 	// used to generate the dev classpath entries
 	// key is bundle ID, value is a model
-	private Map<String, IPluginModelBase> fAllBundles;
+	private Map<String, List<IPluginModelBase>> fAllBundles;
 
 	// key is a model, value is startLevel:autoStart
 	private Map<IPluginModelBase, String> fModels;
@@ -1080,11 +1073,7 @@
 
 		// necessary for PDE to know how to load plugins when target platform = host
 		// platform
-		IPluginModelBase base = fAllBundles.get(PDECore.PLUGIN_ID);
-		if (base != null && VersionUtil.compareMacroMinorMicro(base.getBundleDescription().getVersion(),
-				new Version("3.3.1")) >= 0) { //$NON-NLS-1$
-			vmArgs = concatArg(vmArgs, "-Declipse.pde.launch=true"); //$NON-NLS-1$
-		}
+		vmArgs = concatArg(vmArgs, "-Declipse.pde.launch=true"); //$NON-NLS-1$
 		// For p2 target, add "-Declipse.p2.data.area=@config.dir/p2" unless already
 		// specified by user
 		if (fAllBundles.containsKey("org.eclipse.equinox.p2.core")) { //$NON-NLS-1$
diff --git a/ui/org.eclipse.pde/pom.xml b/ui/org.eclipse.pde/pom.xml
index 00570ad..a9a8b0e 100644
--- a/ui/org.eclipse.pde/pom.xml
+++ b/ui/org.eclipse.pde/pom.xml
@@ -13,11 +13,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.pde</artifactId>
   <version>3.13.1800-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ui/org.eclipse.tools.layout.spy/pom.xml b/ui/org.eclipse.tools.layout.spy/pom.xml
index b8a33e9..b4453f6 100644
--- a/ui/org.eclipse.tools.layout.spy/pom.xml
+++ b/ui/org.eclipse.tools.layout.spy/pom.xml
@@ -13,11 +13,10 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.pde</groupId>
   <artifactId>org.eclipse.tools.layout.spy</artifactId>
   <version>1.1.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
diff --git a/ui/org.eclipse.ui.trace/META-INF/MANIFEST.MF b/ui/org.eclipse.ui.trace/META-INF/MANIFEST.MF
index 0168a89..f8778e6 100644
--- a/ui/org.eclipse.ui.trace/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.ui.trace/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.ui.trace;singleton:=true
-Bundle-Version: 1.2.0.qualifier
+Bundle-Version: 1.2.100.qualifier
 Bundle-Activator: org.eclipse.ui.trace.internal.TracingUIActivator
 Bundle-Vendor: %providerName
 Bundle-RequiredExecutionEnvironment: JavaSE-11
diff --git a/ui/org.eclipse.ui.trace/pom.xml b/ui/org.eclipse.ui.trace/pom.xml
index 9e92982..a87d168 100644
--- a/ui/org.eclipse.ui.trace/pom.xml
+++ b/ui/org.eclipse.ui.trace/pom.xml
@@ -13,12 +13,11 @@
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <artifactId>eclipse.pde.ui</artifactId>
-    <groupId>eclipse.pde.ui</groupId>
+    <groupId>org.eclipse.pde</groupId>
     <version>4.23.0-SNAPSHOT</version>
     <relativePath>../../</relativePath>
   </parent>
-  <groupId>org.eclipse.ui</groupId>
   <artifactId>org.eclipse.ui.trace</artifactId>
-  <version>1.2.0-SNAPSHOT</version>
+  <version>1.2.100-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/ui/org.eclipse.ui.trace/src/org/eclipse/ui/trace/internal/TracingUIActivator.java b/ui/org.eclipse.ui.trace/src/org/eclipse/ui/trace/internal/TracingUIActivator.java
index e7ac18d..b0abd67 100644
--- a/ui/org.eclipse.ui.trace/src/org/eclipse/ui/trace/internal/TracingUIActivator.java
+++ b/ui/org.eclipse.ui.trace/src/org/eclipse/ui/trace/internal/TracingUIActivator.java
@@ -88,10 +88,8 @@
 	 *            The {@link Exception} to log
 	 */
 	public final void logException(final Exception ex) {
-
 		if (ex != null) {
-			final IStatus errorStatus = new Status(IStatus.ERROR, TracingConstants.BUNDLE_ID, ex.getMessage(), ex);
-			this.getLog().log(errorStatus);
+			this.getLog().log(Status.error(ex.getMessage(), ex));
 		}
 	}