Bug 127793 - Improve Headless startup performance (osgi and core runtime side)
diff --git a/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java b/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java
index de73153..b738a6a 100644
--- a/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java
+++ b/bundles/org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/JobActivator.java
@@ -20,6 +20,12 @@
 public class JobActivator implements BundleActivator {
 
 	/**
+	 * Eclipse property. Set to <code>false</code> to avoid registering JobManager
+	 * as an OSGi service.
+	 */
+	private static final String PROP_REGISTER_JOB_SERVICE = "eclipse.service.jobs"; //$NON-NLS-1$
+
+	/**
 	 * The bundle associated this plug-in
 	 */
 	private static BundleContext bundleContext;
@@ -35,7 +41,10 @@
 	public void start(BundleContext context) throws Exception {
 		bundleContext = context;
 		JobOSGiUtils.getDefault().openServices();
-		registerServices();
+
+		boolean shouldRegister = !"false".equalsIgnoreCase(context.getProperty(PROP_REGISTER_JOB_SERVICE)); //$NON-NLS-1$
+		if (shouldRegister)
+			registerServices();
 	}
 
 	/**
diff --git a/bundles/org.eclipse.core.runtime/META-INF/MANIFEST.MF b/bundles/org.eclipse.core.runtime/META-INF/MANIFEST.MF
index 0dc7b68..3e7c9bb 100644
--- a/bundles/org.eclipse.core.runtime/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.core.runtime/META-INF/MANIFEST.MF
@@ -6,14 +6,14 @@
 Bundle-Vendor: %providerName
 Bundle-Activator: org.eclipse.core.internal.runtime.PlatformActivator
 Bundle-Localization: plugin
-Export-Package: org.eclipse.core.internal.preferences;x-internal:=true,
+Export-Package: org.eclipse.core.internal.preferences.legacy;x-internal:=true,
  org.eclipse.core.internal.runtime;x-friends:="org.eclipse.core.runtime.compatibility",
  org.eclipse.core.runtime
-Require-Bundle: org.eclipse.equinox.registry;visibility:=reexport,
+Require-Bundle: org.eclipse.osgi;visibility:=reexport,
+ org.eclipse.equinox.common;visibility:=reexport,
  org.eclipse.core.jobs;visibility:=reexport,
+ org.eclipse.equinox.registry;visibility:=reexport,
  org.eclipse.equinox.preferences;visibility:=reexport,
  org.eclipse.core.contenttype;visibility:=reexport,
- org.eclipse.equinox.common;visibility:=reexport,
- org.eclipse.osgi;bundle-version="[3.1.0,4.0.0)";visibility:=reexport,
  org.eclipse.core.runtime.compatibility.auth;resolution:=optional
 Eclipse-LazyStart: true
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/InitLegacyPreferences.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/legacy/InitLegacyPreferences.java
similarity index 94%
rename from bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/InitLegacyPreferences.java
rename to bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/legacy/InitLegacyPreferences.java
index ff286d9..c726892 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/InitLegacyPreferences.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/legacy/InitLegacyPreferences.java
@@ -8,8 +8,9 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *******************************************************************************/
-package org.eclipse.core.internal.preferences;
+package org.eclipse.core.internal.preferences.legacy;
 
+import org.eclipse.core.internal.preferences.exchange.ILegacyPreferences;
 import org.eclipse.core.internal.runtime.InternalPlatform;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Plugin;
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/PreferenceForwarder.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/legacy/PreferenceForwarder.java
similarity index 99%
rename from bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/PreferenceForwarder.java
rename to bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/legacy/PreferenceForwarder.java
index 8d27ba3..135cb48 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/PreferenceForwarder.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/legacy/PreferenceForwarder.java
@@ -8,11 +8,12 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *******************************************************************************/
-package org.eclipse.core.internal.preferences;
+package org.eclipse.core.internal.preferences.legacy;
 
 import java.io.*;
 import java.util.Iterator;
 import java.util.Properties;
+import org.eclipse.core.internal.preferences.*;
 import org.eclipse.core.internal.runtime.RuntimeLog;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.preferences.DefaultScope;
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/ProductPreferencesService.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/legacy/ProductPreferencesService.java
similarity index 96%
rename from bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/ProductPreferencesService.java
rename to bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/legacy/ProductPreferencesService.java
index ba2cc21..ebf2e4b 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/ProductPreferencesService.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/preferences/legacy/ProductPreferencesService.java
@@ -8,13 +8,14 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *******************************************************************************/
-package org.eclipse.core.internal.preferences;
+package org.eclipse.core.internal.preferences.legacy;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Properties;
+import org.eclipse.core.internal.preferences.exchange.IProductPreferencesService;
 import org.eclipse.core.internal.runtime.InternalPlatform;
 import org.eclipse.core.runtime.*;
 import org.osgi.framework.Bundle;
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/AdapterManagerListener.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/AdapterManagerListener.java
index 7c5fbac..632e377 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/AdapterManagerListener.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/AdapterManagerListener.java
@@ -19,7 +19,7 @@
  * 
  * @since org.eclipse.core.runtime 3.2
  */
-public final class AdapterManagerListener implements IRegistryChangeListener {
+public final class AdapterManagerListener implements IRegistryChangeListener, IAdapterManagerProvider {
 	public static final String ADAPTER_POINT_ID = "org.eclipse.core.runtime.adapters"; //$NON-NLS-1$
 
 	private AdapterManager theAdapterManager;
@@ -29,8 +29,7 @@
 	 */
 	public AdapterManagerListener() {
 		theAdapterManager = AdapterManager.getDefault();
-		registerFactoryProxies();
-		RegistryFactory.getRegistry().addRegistryChangeListener(this);
+		theAdapterManager.registerLazyFactoryProvider(this);
 	}
 
 	/**
@@ -38,13 +37,25 @@
 	 * the plug-in registry.  Note that the actual factory implementations
 	 * are loaded lazily as they are needed.
 	 */
-	private void registerFactoryProxies() {
+	public boolean addFactories(AdapterManager adapterManager) {
 		IExtensionPoint point = RegistryFactory.getRegistry().getExtensionPoint(ADAPTER_POINT_ID);
 		if (point == null)
-			return;
+			return false;
+
+		boolean factoriesAdded = false;
 		IExtension[] extensions = point.getExtensions();
-		for (int i = 0; i < extensions.length; i++)
-			registerExtension(extensions[i]);
+		for (int i = 0; i < extensions.length; i++) {
+			IConfigurationElement[] elements = extensions[i].getConfigurationElements();
+			for (int j = 0; j < elements.length; j++) {
+				AdapterFactoryProxy proxy = AdapterFactoryProxy.createProxy(elements[j]);
+				if (proxy != null) {
+					adapterManager.registerFactory(proxy, proxy.getAdaptableType());
+					factoriesAdded = true;
+				}
+			}
+		}
+		RegistryFactory.getRegistry().addRegistryChangeListener(this);
+		return factoriesAdded;
 	}
 
 	private void registerExtension(IExtension extension) {
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
index 508d688..4864ed7 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
@@ -15,7 +15,10 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.*;
-import org.eclipse.core.internal.preferences.*;
+import org.eclipse.core.internal.preferences.exchange.ILegacyPreferences;
+import org.eclipse.core.internal.preferences.exchange.IProductPreferencesService;
+import org.eclipse.core.internal.preferences.legacy.InitLegacyPreferences;
+import org.eclipse.core.internal.preferences.legacy.ProductPreferencesService;
 import org.eclipse.core.internal.runtime.auth.AuthorizationHandler;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.content.IContentTypeManager;
@@ -110,6 +113,8 @@
 	public static final String PROP_SYSPATH = "osgi.syspath"; //$NON-NLS-1$
 	public static final String PROP_USER_AREA = "osgi.user.area"; //$NON-NLS-1$
 	public static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$
+	public static final String PROP_ACTIVATE_PLUGINS = "eclipse.activateRuntimePlugins"; //$NON-NLS-1$
+
 	private static final InternalPlatform singleton = new InternalPlatform();
 
 	private static final String UPDATE = "-update"; //$NON-NLS-1$
@@ -341,6 +346,16 @@
 
 	public Location getConfigurationLocation() {
 		assertInitialized();
+		if (configurationLocation == null) {
+			Filter filter = null;
+			try {
+				filter = context.createFilter(Location.CONFIGURATION_FILTER);
+			} catch (InvalidSyntaxException e) {
+				// ignore this.  It should never happen as we have tested the above format.
+			}
+			configurationLocation = new ServiceTracker(context, filter, null);
+			configurationLocation.open();
+		}
 		return (Location) configurationLocation.getService();
 	}
 
@@ -360,7 +375,7 @@
 	public EnvironmentInfo getEnvironmentInfoService() {
 		if (environmentTracker == null) {
 			if (context == null)
-				 return null;
+				return null;
 			environmentTracker = new ServiceTracker(context, EnvironmentInfo.class.getName(), null);
 			environmentTracker.open();
 		}
@@ -393,6 +408,16 @@
 
 	public Location getInstallLocation() {
 		assertInitialized();
+		Filter filter = null;
+		if (installLocation == null) {
+			try {
+				filter = context.createFilter(Location.INSTALL_FILTER);
+			} catch (InvalidSyntaxException e) {
+				// ignore this.  It should never happen as we have tested the above format.
+			}
+			installLocation = new ServiceTracker(context, filter, null);
+			installLocation.open();
+		}
 		return (Location) installLocation.getService();
 	}
 
@@ -407,6 +432,16 @@
 
 	public Location getInstanceLocation() {
 		assertInitialized();
+		if (instanceLocation == null) {
+			Filter filter = null;
+			try {
+				filter = context.createFilter(Location.INSTANCE_FILTER);
+			} catch (InvalidSyntaxException e) {
+				// ignore this.  It should never happen as we have tested the above format.
+			}
+			instanceLocation = new ServiceTracker(context, filter, null);
+			instanceLocation.open();
+		}
 		return (Location) instanceLocation.getService();
 	}
 
@@ -633,7 +668,7 @@
 	}
 
 	public IExtensionRegistry getRegistry() {
-		return RegistryFactory.getRegistry(); 
+		return RegistryFactory.getRegistry();
 	}
 
 	public ResourceBundle getResourceBundle(Bundle bundle) {
@@ -693,6 +728,16 @@
 
 	public Location getUserLocation() {
 		assertInitialized();
+		if (userLocation == null) {
+			Filter filter = null;
+			try {
+				filter = context.createFilter(Location.USER_FILTER);
+			} catch (InvalidSyntaxException e) {
+				// ignore this.  It should never happen as we have tested the above format.
+			}
+			userLocation = new ServiceTracker(context, filter, null);
+			userLocation.open();
+		}
 		return (Location) userLocation.getService();
 	}
 
@@ -728,41 +773,6 @@
 		}
 	}
 
-	private void initializeLocationTrackers() {
-		Filter filter = null;
-		try {
-			filter = context.createFilter(Location.CONFIGURATION_FILTER);
-		} catch (InvalidSyntaxException e) {
-			// ignore this.  It should never happen as we have tested the above format.
-		}
-		configurationLocation = new ServiceTracker(context, filter, null);
-		configurationLocation.open();
-
-		try {
-			filter = context.createFilter(Location.USER_FILTER);
-		} catch (InvalidSyntaxException e) {
-			// ignore this.  It should never happen as we have tested the above format.
-		}
-		userLocation = new ServiceTracker(context, filter, null);
-		userLocation.open();
-
-		try {
-			filter = context.createFilter(Location.INSTANCE_FILTER);
-		} catch (InvalidSyntaxException e) {
-			// ignore this.  It should never happen as we have tested the above format.
-		}
-		instanceLocation = new ServiceTracker(context, filter, null);
-		instanceLocation.open();
-
-		try {
-			filter = context.createFilter(Location.INSTALL_FILTER);
-		} catch (InvalidSyntaxException e) {
-			// ignore this.  It should never happen as we have tested the above format.
-		}
-		installLocation = new ServiceTracker(context, filter, null);
-		installLocation.open();
-	}
-
 	public boolean isFragment(Bundle bundle) {
 		PackageAdmin packageAdmin = getBundleAdmin();
 		if (packageAdmin == null)
@@ -967,7 +977,6 @@
 	 */
 	public void start(BundleContext runtimeContext) {
 		this.context = runtimeContext;
-		initializeLocationTrackers();
 		splashHandler = getSplashHandler();
 		processCommandLine(getEnvironmentInfoService().getNonFrameworkArgs());
 		initializeDebugFlags();
@@ -978,6 +987,16 @@
 		addLogListener(platformLog);
 		adapterManagerListener = new AdapterManagerListener(); // after extension registry
 		startServices();
+
+		// See if need to activate rest of the runtime plugins. Plugins are "gently" activated by touching 
+		// a class from the corresponding plugin(s). 
+		boolean shouldActivate = !"false".equalsIgnoreCase(context.getProperty(PROP_ACTIVATE_PLUGINS)); //$NON-NLS-1$
+		if (shouldActivate) {
+			// activate Preferences plugin by creating a class from it:
+			new org.eclipse.core.runtime.preferences.DefaultScope();
+			// activate Jobs plugin by creating a class from it:
+			org.eclipse.core.runtime.jobs.Job.getJobManager();
+		}
 	}
 
 	/**
@@ -1021,8 +1040,14 @@
 	}
 
 	private void startServices() {
+		// The check for getProduct() is relatively expensive (about 3% of the headless startup),
+		// so we don't want to enforce it here. 
 		customPreferencesService = getBundleContext().registerService(IProductPreferencesService.class.getName(), new ProductPreferencesService(), new Hashtable());
-		legacyPreferencesService = getBundleContext().registerService(ILegacyPreferences.class.getName(), new InitLegacyPreferences(), new Hashtable());
+
+		// Only register this interface if compatibility is installed - the check for a bundle presence
+		// is a quick test that doesn't consume much.
+		if (getBundle(CompatibilityHelper.PI_RUNTIME_COMPATIBILITY) != null)
+			legacyPreferencesService = getBundleContext().registerService(ILegacyPreferences.class.getName(), new InitLegacyPreferences(), new Hashtable());
 	}
 
 	private void stopServices() {
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Plugin.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Plugin.java
index f9c8640..e64c91d 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Plugin.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Plugin.java
@@ -15,12 +15,9 @@
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.util.Map;
-import org.eclipse.core.internal.preferences.PreferenceForwarder;
 import org.eclipse.core.internal.runtime.*;
-import org.eclipse.core.runtime.preferences.InstanceScope;
 import org.eclipse.osgi.util.NLS;
 import org.osgi.framework.*;
-import org.osgi.service.prefs.BackingStoreException;
 
 /**
  * The abstract superclass of all plug-in runtime class
@@ -107,11 +104,14 @@
 
 	/**
 	 * String constant used for the default scope name for legacy 
-	 * Eclipse plug-in preferences. 
+	 * Eclipse plug-in preferences. The value of <code>PLUGIN_PREFERENCE_SCOPE</code> should
+	 * match the InstanceScope's variable SCOPE from org.eclipse.core.runtime.preferences.
+	 * The value is copied in this file to prevent unnecessary activation of
+	 * the Preferences plugin on startup.
 	 * 
 	 * @since 3.0
 	 */
-	public static final String PLUGIN_PREFERENCE_SCOPE = InstanceScope.SCOPE;
+	public static final String PLUGIN_PREFERENCE_SCOPE = "instance"; //$NON-NLS-1$
 
 	/**
 	 * The bundle associated this plug-in
@@ -158,7 +158,7 @@
 	 * 
 	 * @since 2.0
 	 */
-	private PreferenceForwarder preferences = null;
+	private Preferences preferences = null;
 
 	/**
 	 * Creates a new plug-in runtime object.  This method is called by the platform
@@ -338,7 +338,19 @@
 
 		if (InternalPlatform.DEBUG_PLUGIN_PREFERENCES)
 			InternalPlatform.message("Loading preferences for plugin: " + bundle.getSymbolicName()); //$NON-NLS-1$
-		preferences = new PreferenceForwarder(this, bundle.getSymbolicName());
+
+		// Performance: isolate PreferenceForwarder into an inner class so that it mere presence
+		// won't force the PreferenceForwarder class to be loaded (which triggers Preferences plugin
+		// activation).
+		final Bundle bundleCopy = bundle;
+		final Preferences[] preferencesCopy = new Preferences[1];
+		Runnable innerCall = new Runnable() {
+			public void run() {
+				preferencesCopy[0] = new org.eclipse.core.internal.preferences.legacy.PreferenceForwarder(this, bundleCopy.getSymbolicName());
+			}
+		};
+		innerCall.run();
+		preferences = preferencesCopy[0];
 		return preferences;
 	}
 
@@ -359,12 +371,22 @@
 		// need to save them because someone else might have
 		// made changes via the OSGi APIs.
 		getPluginPreferences();
-		try {
-			preferences.flush();
-		} catch (BackingStoreException e) {
-			IStatus status = new Status(IStatus.ERROR, Platform.PI_RUNTIME, IStatus.ERROR, Messages.preferences_saveProblems, e);
-			InternalPlatform.getDefault().log(status);
-		}
+
+		// Performance: isolate PreferenceForwarder and BackingStoreException into 
+		// an inner class to avoid class loading (and then activation of the Preferences plugin)
+		// as the Plugin class is loaded.
+		final Preferences preferencesCopy = preferences;
+		Runnable innerCall = new Runnable() {
+			public void run() {
+				try {
+					((org.eclipse.core.internal.preferences.legacy.PreferenceForwarder) preferencesCopy).flush();
+				} catch (org.osgi.service.prefs.BackingStoreException e) {
+					IStatus status = new Status(IStatus.ERROR, Platform.PI_RUNTIME, IStatus.ERROR, Messages.preferences_saveProblems, e);
+					InternalPlatform.getDefault().log(status);
+				}
+			}
+		};
+		innerCall.run();
 	}
 
 	/**
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/PreferenceForwarderTest.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/PreferenceForwarderTest.java
index 4a113aa..770ca14 100644
--- a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/PreferenceForwarderTest.java
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/PreferenceForwarderTest.java
@@ -14,7 +14,7 @@
 import java.util.*;
 import junit.framework.Test;
 import junit.framework.TestSuite;
-import org.eclipse.core.internal.preferences.PreferenceForwarder;
+import org.eclipse.core.internal.preferences.legacy.PreferenceForwarder;
 import org.eclipse.core.runtime.Preferences;
 
 /**
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/perf/AllTests.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/perf/AllTests.java
index ff0ab39..f503ab8 100644
--- a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/perf/AllTests.java
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/perf/AllTests.java
@@ -30,7 +30,16 @@
 			fail("Unable to create warm up test");
 		}
 
-		suite.addTest(new PerformanceSessionTestSuite(RuntimeTestsPlugin.PI_RUNTIME_TESTS, 5, StartupTest.class));
+		// For this test to take advantage of the new runtime processing, we set "-eclipse.activateRuntimePlugins=false"
+		try {
+			PerformanceSessionTestSuite headlessSuite = new PerformanceSessionTestSuite(RuntimeTestsPlugin.PI_RUNTIME_TESTS, 5, StartupTest.class);
+			Setup headlessSetup = headlessSuite.getSetup();
+			headlessSetup.setSystemProperty("eclipse.activateRuntimePlugins", "false");
+			suite.addTest(headlessSuite);
+		} catch (SetupException e) {
+			fail("Unable to setup headless startup performance test");
+		}
+		
 		suite.addTest(new UIPerformanceSessionTestSuite(RuntimeTestsPlugin.PI_RUNTIME_TESTS, 5, UIStartupTest.class));
 		suite.addTest(BenchPath.suite());
 		suite.addTest(ContentTypePerformanceTest.suite());