/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 400714, 441267, 441184, 445723, 445724, 472654, 481608
 *     Patrik Suzzi <psuzzi@gmail.com> - Bug 489250
 *******************************************************************************/

package org.eclipse.ui.internal;

import java.lang.reflect.InvocationTargetException;
import java.text.Bidi;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
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.e4.core.commands.internal.ICommandHelpService;
import org.eclipse.e4.core.contexts.ContextFunction;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.ui.services.help.EHelpService;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.util.BidiUtils;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IDecoratorManager;
import org.eclipse.ui.IEditorRegistry;
import org.eclipse.ui.IElementFactory;
import org.eclipse.ui.IPerspectiveRegistry;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkingSetManager;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.decorators.DecoratorManager;
import org.eclipse.ui.internal.dialogs.WorkbenchPreferenceManager;
import org.eclipse.ui.internal.help.CommandHelpServiceImpl;
import org.eclipse.ui.internal.help.HelpServiceImpl;
import org.eclipse.ui.internal.intro.IIntroRegistry;
import org.eclipse.ui.internal.intro.IntroRegistry;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.operations.WorkbenchOperationSupport;
import org.eclipse.ui.internal.progress.ProgressManager;
import org.eclipse.ui.internal.registry.ActionSetRegistry;
import org.eclipse.ui.internal.registry.EditorRegistry;
import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
import org.eclipse.ui.internal.registry.PerspectiveRegistry;
import org.eclipse.ui.internal.registry.PreferencePageRegistryReader;
import org.eclipse.ui.internal.registry.ViewRegistry;
import org.eclipse.ui.internal.registry.WorkingSetRegistry;
import org.eclipse.ui.internal.themes.IThemeRegistry;
import org.eclipse.ui.internal.themes.ThemeRegistry;
import org.eclipse.ui.internal.themes.ThemeRegistryReader;
import org.eclipse.ui.internal.util.BundleUtility;
import org.eclipse.ui.internal.wizards.ExportWizardRegistry;
import org.eclipse.ui.internal.wizards.ImportWizardRegistry;
import org.eclipse.ui.internal.wizards.NewWizardRegistry;
import org.eclipse.ui.operations.IWorkbenchOperationSupport;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.testing.TestableObject;
import org.eclipse.ui.views.IViewRegistry;
import org.eclipse.ui.wizards.IWizardRegistry;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleListener;
import org.osgi.util.tracker.ServiceTracker;

/**
 * This class represents the TOP of the workbench UI world A plugin class is
 * effectively an application wrapper for a plugin &amp; its classes. This class
 * should be thought of as the workbench UI's application class.
 *
 * This class is responsible for tracking various registries font, preference,
 * graphics, dialog store.
 *
 * This class is explicitly referenced by the workbench plugin's "plugin.xml"
 * and places it into the UI start extension point of the main overall
 * application harness
 *
 * When is this class started? When the Application calls
 * createExecutableExtension to create an executable instance of our workbench
 * class.
 */
public class WorkbenchPlugin extends AbstractUIPlugin {

	/**
	 * Splash shell constant.
	 */
	private static final String DATA_SPLASH_SHELL = "org.eclipse.ui.workbench.splashShell"; //$NON-NLS-1$

	/**
	 * The OSGi splash property.
	 *
	 * @since 3.4
	 */
	private static final String PROP_SPLASH_HANDLE = "org.eclipse.equinox.launcher.splash.handle"; //$NON-NLS-1$

	private static final String LEFT_TO_RIGHT = "ltr"; //$NON-NLS-1$
	private static final String RIGHT_TO_LEFT = "rtl";//$NON-NLS-1$
	private static final String ORIENTATION_COMMAND_LINE = "-dir";//$NON-NLS-1$
	private static final String ORIENTATION_PROPERTY = "eclipse.orientation";//$NON-NLS-1$
	private static final String NL_USER_PROPERTY = "osgi.nl.user"; //$NON-NLS-1$
	private static final String BIDI_COMMAND_LINE = "-bidi";//$NON-NLS-1$
	private static final String BIDI_SUPPORT_OPTION = "on";//$NON-NLS-1$
	private static final String BIDI_TEXTDIR_OPTION = "textDir";//$NON-NLS-1$

	// Default instance of the receiver
	private static WorkbenchPlugin inst;

	// Manager that maps resources to descriptors of editors to use
	private EditorRegistry editorRegistry;

	// Manager for the DecoratorManager
	private DecoratorManager decoratorManager;

	// Theme registry
	private ThemeRegistry themeRegistry;

	// Manager for working sets (IWorkingSet)
	private WorkingSetManager workingSetManager;

	// Working set registry, stores working set dialogs
	private WorkingSetRegistry workingSetRegistry;

	// The context within which this plugin was started.
	private BundleContext bundleContext;

	// The set of currently starting bundles
	private Collection<Bundle> startingBundles = new HashSet<>();

	/**
	 * Global workbench ui plugin flag. Only workbench implementation is allowed to
	 * use this flag All other plugins, examples, or test cases must *not* use this
	 * flag.
	 */
	public static boolean DEBUG = false;

	/**
	 * The workbench plugin ID.
	 *
	 * @issue we should just drop this constant and use PlatformUI.PLUGIN_ID instead
	 */
	public static String PI_WORKBENCH = PlatformUI.PLUGIN_ID;

	/**
	 * The character used to separate preference page category ids
	 */
	public static char PREFERENCE_PAGE_CATEGORY_SEPARATOR = '/';

	// Other data.
	private WorkbenchPreferenceManager preferenceManager;

	private ViewRegistry viewRegistry;

	private PerspectiveRegistry perspRegistry;

	private ActionSetRegistry actionSetRegistry;

	private SharedImages sharedImages;

	/**
	 * Information describing the product (formerly called "primary plugin"); lazily
	 * initialized.
	 *
	 * @since 3.0
	 */
	private ProductInfo productInfo = null;

	private IntroRegistry introRegistry;

	private WorkbenchOperationSupport operationSupport;
	private BundleListener bundleListener;

	private IEclipseContext e4Context;

	private ServiceTracker debugTracker = null;

	private ServiceTracker testableTracker = null;

	private EHelpService helpService;

	private ICommandHelpService commandHelpService;

	/**
	 * Create an instance of the WorkbenchPlugin. The workbench plugin is
	 * effectively the "application" for the workbench UI. The entire UI operates as
	 * a good plugin citizen.
	 */
	public WorkbenchPlugin() {
		super();
		inst = this;
	}

	/**
	 * Unload all members. This can be used to run a second instance of a workbench.
	 *
	 * @since 3.0
	 */
	void reset() {
		editorRegistry = null;

		if (decoratorManager != null) {
			decoratorManager.shutdown();
			decoratorManager = null;
		}

		ProgressManager.shutdownProgressManager();

		themeRegistry = null;
		if (workingSetManager != null) {
			workingSetManager.dispose();
			workingSetManager = null;
		}
		workingSetRegistry = null;

		preferenceManager = null;
		if (viewRegistry != null) {
			// nothing to dispose for viewRegistry
			viewRegistry = null;
		}
		if (perspRegistry != null) {
			perspRegistry.dispose();
			perspRegistry = null;
		}
		actionSetRegistry = null;
		sharedImages = null;

		productInfo = null;
		introRegistry = null;

		helpService = null;
		commandHelpService = null;

		if (operationSupport != null) {
			operationSupport.dispose();
			operationSupport = null;
		}

		DEBUG = false;

	}

	/**
	 * Creates an extension. If the extension plugin has not been loaded a busy
	 * cursor will be activated during the duration of the load.
	 *
	 * @param element        the config element defining the extension
	 * @param classAttribute the name of the attribute carrying the class
	 * @return the extension object
	 * @throws CoreException if the extension cannot be created
	 */
	public static Object createExtension(final IConfigurationElement element, final String classAttribute)
			throws CoreException {
		try {
			// If plugin has been loaded create extension.
			// Otherwise, show busy cursor then create extension.
			if (BundleUtility.isActivated(element.getContributor().getName())) {
				return element.createExecutableExtension(classAttribute);
			}
			final Object[] ret = new Object[1];
			final CoreException[] exc = new CoreException[1];
			BusyIndicator.showWhile(null, () -> {
				try {
					ret[0] = element.createExecutableExtension(classAttribute);
				} catch (CoreException e) {
					exc[0] = e;
				}
			});
			if (exc[0] != null) {
				throw exc[0];
			}
			return ret[0];

		} catch (CoreException core) {
			throw core;
		} catch (Exception e) {
			throw new CoreException(new Status(IStatus.ERROR, PI_WORKBENCH, IStatus.ERROR,
					WorkbenchMessages.WorkbenchPlugin_extension, e));
		}
	}

	/**
	 * Answers whether the provided element either has an attribute with the given
	 * name or a child element with the given name with an attribute called class.
	 *
	 * @param element       the element to test
	 * @param extensionName the name of the extension to test for
	 * @return whether or not the extension is declared
	 * @since 3.3
	 */
	public static boolean hasExecutableExtension(IConfigurationElement element, String extensionName) {

		if (element.getAttribute(extensionName) != null)
			return true;
		String elementText = element.getValue();
		if (elementText != null && !elementText.isEmpty())
			return true;
		IConfigurationElement[] children = element.getChildren(extensionName);
		if (children.length == 1) {
			if (children[0].getAttribute(IWorkbenchRegistryConstants.ATT_CLASS) != null)
				return true;
		}
		return false;
	}

	/**
	 * Checks to see if the provided element has the syntax for an executable
	 * extension with a given name that resides in a bundle that is already active.
	 * Determining the bundle happens in one of two ways:<br>
	 * <ul>
	 * <li>The element has an attribute with the specified name or element text in
	 * the form <code>bundle.id/class.name[:optional attributes]</code></li>
	 * <li>The element has a child element with the specified name that has a
	 * <code>plugin</code> attribute</li>
	 * </ul>
	 *
	 * @param element       the element to test
	 * @param extensionName the name of the extension to test for
	 * @return whether or not the bundle expressed by the above criteria is active.
	 *         If the bundle cannot be determined then the state of the bundle that
	 *         declared the element is returned.
	 * @since 3.3
	 */
	public static boolean isBundleLoadedForExecutableExtension(IConfigurationElement element, String extensionName) {
		Bundle bundle = getBundleForExecutableExtension(element, extensionName);

		if (bundle == null)
			return true;
		return bundle.getState() == Bundle.ACTIVE;
	}

	/**
	 * Returns the bundle that contains the class referenced by an executable
	 * extension. Determining the bundle happens in one of two ways:<br>
	 * <ul>
	 * <li>The element has an attribute with the specified name or element text in
	 * the form <code>bundle.id/class.name[:optional attributes]</code></li>
	 * <li>The element has a child element with the specified name that has a
	 * <code>plugin</code> attribute</li>
	 * </ul>
	 *
	 * @param element       the element to test
	 * @param extensionName the name of the extension to test for
	 * @return the bundle referenced by the extension. If that bundle cannot be
	 *         determined the bundle that declared the element is returned. Note
	 *         that this may be <code>null</code>.
	 * @since 3.3
	 */
	public static Bundle getBundleForExecutableExtension(IConfigurationElement element, String extensionName) {
		// this code is derived heavily from
		// ConfigurationElement.createExecutableExtension.
		String prop = null;
		String executable;
		String contributorName = null;
		int i;

		if (extensionName != null)
			prop = element.getAttribute(extensionName);
		else {
			// property not specified, try as element value
			prop = element.getValue();
			if (prop != null) {
				prop = prop.trim();
				if (prop.isEmpty())
					prop = null;
			}
		}

		if (prop == null) {
			// property not defined, try as a child element
			IConfigurationElement[] exec = element.getChildren(extensionName);
			if (exec.length != 0)
				contributorName = exec[0].getAttribute("plugin"); //$NON-NLS-1$
		} else {
			// simple property or element value, parse it into its components
			i = prop.indexOf(':');
			if (i != -1)
				executable = prop.substring(0, i).trim();
			else
				executable = prop;

			i = executable.indexOf('/');
			if (i != -1)
				contributorName = executable.substring(0, i).trim();

		}

		if (contributorName == null)
			contributorName = element.getContributor().getName();

		return Platform.getBundle(contributorName);
	}

	/**
	 * Returns the image registry for this plugin.
	 *
	 * Where are the images? The images (typically gifs) are found in the same
	 * plugins directory.
	 *
	 * @see ImageRegistry
	 *
	 *      Note: The workbench uses the standard JFace ImageRegistry to track its
	 *      images. In addition the class WorkbenchGraphicResources provides
	 *      convenience access to the graphics resources and fast field access for
	 *      some of the commonly used graphical images.
	 */
	@Override
	protected ImageRegistry createImageRegistry() {
		return WorkbenchImages.getImageRegistry();
	}

	/**
	 * Returns the action set registry for the workbench.
	 *
	 * @return the workbench action set registry
	 */
	public ActionSetRegistry getActionSetRegistry() {
		return e4Context.get(ActionSetRegistry.class);
	}

	/**
	 * Return the default instance of the receiver. This represents the runtime
	 * plugin.
	 *
	 * @return WorkbenchPlugin
	 * @see AbstractUIPlugin for the typical implementation pattern for plugin
	 *      classes.
	 */
	public static WorkbenchPlugin getDefault() {
		return inst;
	}

	/**
	 * Answer the manager that maps resource types to a the description of the
	 * editor to use
	 *
	 * @return IEditorRegistry the editor registry used by this plug-in.
	 */

	public IEditorRegistry getEditorRegistry() {
		return e4Context.get(IEditorRegistry.class);
	}

	/**
	 * Answer the element factory for an id, or <code>null</code>. if not found.
	 *
	 * @param targetID
	 * @return IElementFactory
	 */
	public IElementFactory getElementFactory(String targetID) {

		// Get the extension point registry.
		IExtensionPoint extensionPoint;
		extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(PI_WORKBENCH,
				IWorkbenchRegistryConstants.PL_ELEMENT_FACTORY);

		if (extensionPoint == null) {
			WorkbenchPlugin.log("Unable to find element factory. Extension point: " //$NON-NLS-1$
					+ IWorkbenchRegistryConstants.PL_ELEMENT_FACTORY + " not found"); //$NON-NLS-1$
			return null;
		}

		// Loop through the config elements.
		IConfigurationElement targetElement = null;
		IConfigurationElement[] configElements = extensionPoint.getConfigurationElements();
		for (IConfigurationElement configElement : configElements) {
			String strID = configElement.getAttribute("id"); //$NON-NLS-1$
			if (targetID.equals(strID)) {
				targetElement = configElement;
				break;
			}
		}
		if (targetElement == null) {
			// log it since we cannot safely display a dialog.
			WorkbenchPlugin.log("Unable to find element factory: " + targetID); //$NON-NLS-1$
			return null;
		}

		// Create the extension.
		IElementFactory factory = null;
		try {
			factory = (IElementFactory) createExtension(targetElement, "class"); //$NON-NLS-1$
		} catch (CoreException e) {
			// log it since we cannot safely display a dialog.
			WorkbenchPlugin.log("Unable to create element factory.", e.getStatus()); //$NON-NLS-1$
			factory = null;
		}
		return factory;
	}

	/**
	 * Return the perspective registry.
	 *
	 * @return IPerspectiveRegistry. The registry for the receiver.
	 */
	public IPerspectiveRegistry getPerspectiveRegistry() {
		return e4Context.get(IPerspectiveRegistry.class);
	}

	/**
	 * Returns the working set manager
	 *
	 * @return the working set manager
	 * @since 2.0
	 */
	public IWorkingSetManager getWorkingSetManager() {
		return e4Context.get(IWorkingSetManager.class);
	}

	/**
	 * Returns the working set registry
	 *
	 * @return the working set registry
	 * @since 2.0
	 */
	public WorkingSetRegistry getWorkingSetRegistry() {
		return e4Context.get(WorkingSetRegistry.class);
	}

	/**
	 * Returns the introduction registry.
	 *
	 * @return the introduction registry.
	 * @since 3.0
	 */
	public IIntroRegistry getIntroRegistry() {
		return e4Context.get(IIntroRegistry.class);
	}

	/**
	 * Returns the operation support.
	 *
	 * @return the workbench operation support.
	 * @since 3.1
	 */
	public IWorkbenchOperationSupport getOperationSupport() {
		IWorkbenchOperationSupport op = e4Context.get(IWorkbenchOperationSupport.class);
		if (op == null) {
			// we're in shutdown, but some plugins get this in their stop()
			// methods. In 3.x we just return a bogus one, so here it is
			op = new WorkbenchOperationSupport();
		}
		return op;
	}

	/**
	 * Get the preference manager.
	 *
	 * @return PreferenceManager the preference manager for the receiver.
	 */
	public PreferenceManager getPreferenceManager() {
		return e4Context.get(PreferenceManager.class);
	}

	/**
	 * Returns the shared images for the workbench.
	 *
	 * @return the shared image manager
	 */
	public ISharedImages getSharedImages() {
		if (sharedImages == null) {
			sharedImages = new SharedImages();
		}
		if (e4Context == null) {
			return sharedImages;
		}
		return e4Context.get(ISharedImages.class);
	}

	/**
	 * Returns the theme registry for the workbench.
	 *
	 * @return the theme registry
	 */
	public IThemeRegistry getThemeRegistry() {
		return e4Context.get(IThemeRegistry.class);
	}

	/**
	 * Answer the view registry.
	 *
	 * @return IViewRegistry the view registry for the receiver.
	 */
	public IViewRegistry getViewRegistry() {
		return e4Context.get(IViewRegistry.class);
	}

	/**
	 * Set default preference values. This method must be called whenever the
	 * preference store is initially loaded because the default values are not
	 * stored in the preference store.
	 */
	@Override
	protected void initializeDefaultPreferences(IPreferenceStore store) {
		// Do nothing. This should not be called.
		// Prefs are initialized in WorkbenchPreferenceInitializer.
	}

	/**
	 * Logs the given message to the platform log.
	 *
	 * If you have an exception in hand, call log(String, Throwable) instead.
	 *
	 * If you have a status object in hand call log(String, IStatus) instead.
	 *
	 * This convenience method is for internal use by the Workbench only and must
	 * not be called outside the Workbench.
	 *
	 * @param message A high level UI message describing when the problem happened.
	 */
	public static void log(String message) {
		getDefault().getLog().log(StatusUtil.newStatus(IStatus.ERROR, message, null));
	}

	/**
	 * Log the throwable.
	 *
	 * @param t
	 */
	public static void log(Throwable t) {
		getDefault().getLog().log(getStatus(t));
	}

	/**
	 * Return the status from throwable
	 *
	 * @param t throwable
	 * @return IStatus
	 */
	public static IStatus getStatus(Throwable t) {
		String message = StatusUtil.getLocalizedMessage(t);

		return newError(message, t);
	}

	/**
	 * Create a new error from the message and the throwable.
	 *
	 * @param message
	 * @param t
	 * @return IStatus
	 */
	public static IStatus newError(String message, Throwable t) {
		String pluginId = "org.eclipse.ui.workbench"; //$NON-NLS-1$
		int errorCode = IStatus.OK;

		// If this was a CoreException, keep the original plugin ID and error
		// code
		if (t instanceof CoreException) {
			CoreException ce = (CoreException) t;
			pluginId = ce.getStatus().getPlugin();
			errorCode = ce.getStatus().getCode();
		}

		return new Status(IStatus.ERROR, pluginId, errorCode, message, t);
	}

	/**
	 * Logs the given message and throwable to the platform log.
	 *
	 * If you have a status object in hand call log(String, IStatus) instead.
	 *
	 * This convenience method is for internal use by the Workbench only and must
	 * not be called outside the Workbench.
	 *
	 * @param message A high level UI message describing when the problem happened.
	 * @param t       The throwable from where the problem actually occurred.
	 */
	public static void log(String message, Throwable t) {
		IStatus status = StatusUtil.newStatus(IStatus.ERROR, message, t);
		log(message, status);
	}

	/**
	 * Logs the given throwable to the platform log, indicating the class and method
	 * from where it is being logged (this is not necessarily where it occurred).
	 *
	 * This convenience method is for internal use by the Workbench only and must
	 * not be called outside the Workbench.
	 *
	 * @param clazz      The calling class.
	 * @param methodName The calling method name.
	 * @param t          The throwable from where the problem actually occurred.
	 */
	public static void log(Class clazz, String methodName, Throwable t) {
		String msg = MessageFormat.format("Exception in {0}.{1}: {2}", //$NON-NLS-1$
				clazz.getName(), methodName, t);
		log(msg, t);
	}

	/**
	 * Logs the given message and status to the platform log.
	 *
	 * This convenience method is for internal use by the Workbench only and must
	 * not be called outside the Workbench.
	 *
	 * @param message A high level UI message describing when the problem happened.
	 *                May be <code>null</code>.
	 * @param status  The status describing the problem. Must not be null.
	 */
	public static void log(String message, IStatus status) {

		// 1FTUHE0: ITPCORE:ALL - API - Status & logging - loss of semantic info

		// Combine message and status into a MultiStatus to avoid losing
		// context, but avoid creating the MultiStatus unnecessarily if message
		// is the same
		if (message != null && !message.equals(status.getMessage())) {
			status = StatusUtil.newStatus(Collections.singletonList(status), message, null);
		}
		getDefault().getLog().log(status);
	}

	/**
	 * Log the status to the default log.
	 *
	 * @param status
	 */
	public static void log(IStatus status) {
		getDefault().getLog().log(status);
	}

	/**
	 * Get the decorator manager for the receiver
	 *
	 * @return DecoratorManager the decorator manager for the receiver.
	 */
	public DecoratorManager getDecoratorManager() {
		return (DecoratorManager) e4Context.get(IDecoratorManager.class);
	}

	@Override
	public void start(BundleContext context) throws Exception {
		context.addBundleListener(getBundleListener());
		super.start(context);
		bundleContext = context;

		JFaceUtil.initializeJFace();

		parseBidiArguments();
		Window.setDefaultOrientation(getDefaultOrientation());

		// The UI plugin needs to be initialized so that it can install the callback in
		// PrefUtil,
		// which needs to be done as early as possible, before the workbench
		// accesses any API preferences.
		Bundle uiBundle = Platform.getBundle(PlatformUI.PLUGIN_ID);
		try {
			// Attempt to load the activator of the ui bundle. This will force lazy start
			// of the ui bundle. Using the bundle activator class here because it is a
			// class that needs to be loaded anyway so it should not cause extra classes
			// to be loaded.s
			if (uiBundle != null)
				uiBundle.start(Bundle.START_TRANSIENT);
		} catch (BundleException e) {
			WorkbenchPlugin.log("Unable to load UI activator", e); //$NON-NLS-1$
		}
		/*
		 * DO NOT RUN ANY OTHER CODE AFTER THIS LINE. If you do, then you are likely to
		 * cause a deadlock in class loader code. Please see Bug 86450 for more
		 * information.
		 */

	}

	/**
	 * Read the -bidi option from the command line arguments. The valid values /
	 * syntax is as follows:
	 *
	 * <pre>
	 * -bidi "on=[y/n];textDir=[ltr/rtl/auto]"
	 * </pre>
	 * <p>
	 * Important:
	 * <ul>
	 * <li>The order of parameters under the <code>-bidi</code> switch is
	 * arbitrary.</li>
	 * <li>The presence of any parameter is not mandatory.</li>
	 * <li>If any of the parameters is not specified, the default value is assumed.
	 * Defaults:
	 * <ul>
	 * <li>on: n</li>
	 * <li>textDir: no default value</li>
	 * </ul>
	 * </li>
	 * <li>If no value (or an illegal value) is provided for handling of base text
	 * direction functionality, then bidi support is turned off and no handling
	 * occurs.</li>
	 * </ul>
	 */
	private void parseBidiArguments() {
		String[] commandLineArgs = Platform.getCommandLineArgs();
		String bidiParams = null;
		// Do not process the last one as it will never have a parameter
		for (int i = 0; i < commandLineArgs.length - 1; i++) {
			if (commandLineArgs[i].equals(BIDI_COMMAND_LINE)) {
				bidiParams = commandLineArgs[i + 1];
			}
		}
		if (bidiParams != null) {
			String[] bidiProps = bidiParams.split(";"); //$NON-NLS-1$
			for (String bidiProp : bidiProps) {
				int eqPos = bidiProp.indexOf('=');
				if ((eqPos > 0) && (eqPos < bidiProp.length() - 1)) {
					String nameProp = bidiProp.substring(0, eqPos);
					String valProp = bidiProp.substring(eqPos + 1);
					if (nameProp.equals(BIDI_SUPPORT_OPTION)) {
						BidiUtils.setBidiSupport("y".equals(valProp)); //$NON-NLS-1$
					} else if (nameProp.equalsIgnoreCase(BIDI_TEXTDIR_OPTION)) {
						try {
							BidiUtils.setTextDirection(valProp.intern());
						} catch (IllegalArgumentException e) {
							WorkbenchPlugin.log(e);
						}
					}
				}
			}
		}
	}

	/**
	 * Get the default orientation from the command line arguments. If there are no
	 * arguments imply the orientation.
	 *
	 * @return int
	 * @see SWT#NONE
	 * @see SWT#RIGHT_TO_LEFT
	 * @see SWT#LEFT_TO_RIGHT
	 */
	private int getDefaultOrientation() {

		String[] commandLineArgs = Platform.getCommandLineArgs();

		int orientation = getCommandLineOrientation(commandLineArgs);

		if (orientation != SWT.NONE) {
			return orientation;
		}

		orientation = getSystemPropertyOrientation();

		if (orientation != SWT.NONE) {
			return orientation;
		}

		return checkCommandLineLocale(); // Use the default value if there is nothing specified
	}

	/**
	 * Check whether the workbench messages are in a Bidi language. This method will
	 * return <code>null</code> if it is unable to determine message properties.
	 */
	private Boolean isBidiMessageText() {
		// Check if the user installed the NLS packs for bidi
		String message = WorkbenchMessages.Startup_Loading_Workbench;
		if (message == null)
			return null;

		try {
			// use qualified class name to avoid import statement
			// and premature attempt to resolve class reference
			boolean isBidi = Bidi.requiresBidi(message.toCharArray(), 0, message.length());
			return Boolean.valueOf(isBidi);
		} catch (NoClassDefFoundError e) {
			// the ICU Base bundle used in place of ICU?
			return null;
		}
	}

	/**
	 * Check to see if the command line parameter for -nl has been set. If so imply
	 * the orientation from this specified Locale. If it is a bidirectional Locale
	 * return SWT#RIGHT_TO_LEFT. If it has not been set or has been set to a
	 * unidirectional Locale then return SWT#NONE.
	 *
	 * Locale is determined differently by different JDKs and may not be consistent
	 * with the users expectations.
	 *
	 *
	 * @return int
	 * @see SWT#NONE
	 * @see SWT#RIGHT_TO_LEFT
	 */
	private int checkCommandLineLocale() {
		// Check if the user property is set. If not, do not rely on the VM.
		if (System.getProperty(NL_USER_PROPERTY) == null) {
			Boolean needRTL = isBidiMessageText();
			if (needRTL != null && needRTL.booleanValue())
				return SWT.RIGHT_TO_LEFT;
		} else {
			String lang = Locale.getDefault().getLanguage();
			boolean bidiLangauage = "iw".equals(lang) || "he".equals(lang) || "ar".equals(lang) || //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					"fa".equals(lang) || "ur".equals(lang); //$NON-NLS-1$ //$NON-NLS-2$
			if (bidiLangauage) {
				Boolean needRTL = isBidiMessageText();
				if (needRTL == null)
					return SWT.RIGHT_TO_LEFT;
				if (needRTL.booleanValue())
					return SWT.RIGHT_TO_LEFT;
			}
		}
		return SWT.NONE;
	}

	/**
	 * Check to see if the orientation was set in the system properties. If there is
	 * no orientation specified return SWT#NONE.
	 *
	 * @return int
	 * @see SWT#NONE
	 * @see SWT#RIGHT_TO_LEFT
	 * @see SWT#LEFT_TO_RIGHT
	 */
	private int getSystemPropertyOrientation() {
		String orientation = System.getProperty(ORIENTATION_PROPERTY);
		if (RIGHT_TO_LEFT.equals(orientation)) {
			return SWT.RIGHT_TO_LEFT;
		}
		if (LEFT_TO_RIGHT.equals(orientation)) {
			return SWT.LEFT_TO_RIGHT;
		}
		return SWT.NONE;
	}

	/**
	 * Find the orientation in the commandLineArgs. If there is no orientation
	 * specified return SWT#NONE.
	 *
	 * @param commandLineArgs
	 * @return int
	 * @see SWT#NONE
	 * @see SWT#RIGHT_TO_LEFT
	 * @see SWT#LEFT_TO_RIGHT
	 */
	private int getCommandLineOrientation(String[] commandLineArgs) {
		// Do not process the last one as it will never have a parameter
		for (int i = 0; i < commandLineArgs.length - 1; i++) {
			if (commandLineArgs[i].equalsIgnoreCase(ORIENTATION_COMMAND_LINE)) {
				String orientation = commandLineArgs[i + 1];
				if (orientation.equals(RIGHT_TO_LEFT)) {
					System.setProperty(ORIENTATION_PROPERTY, RIGHT_TO_LEFT);
					return SWT.RIGHT_TO_LEFT;
				}
				if (orientation.equals(LEFT_TO_RIGHT)) {
					System.setProperty(ORIENTATION_PROPERTY, LEFT_TO_RIGHT);
					return SWT.LEFT_TO_RIGHT;
				}
			}
		}

		return SWT.NONE;
	}

	/**
	 * Return an array of all bundles contained in this workbench.
	 *
	 * @return an array of bundles in the workbench or an empty array if none
	 * @since 3.0
	 */
	public Bundle[] getBundles() {
		return bundleContext == null ? new Bundle[0] : bundleContext.getBundles();
	}

	/**
	 * Returns the bundle context associated with the workbench plug-in.
	 *
	 * @return the bundle context
	 * @since 3.1
	 */
	public BundleContext getBundleContext() {
		return bundleContext;
	}

	/**
	 * Returns the application name.
	 * <p>
	 * Note this is never shown to the user. It is used to initialize the SWT
	 * Display. On Motif, for example, this can be used to set the name used for
	 * resource lookup.
	 * </p>
	 *
	 * @return the application name, or <code>null</code>
	 * @see org.eclipse.swt.widgets.Display#setAppName
	 * @since 3.0
	 */
	public String getAppName() {
		return getProductInfo().getAppName();
	}

	/**
	 * Returns the name of the product.
	 *
	 * @return the product name, or <code>null</code> if none
	 * @since 3.0
	 */
	public String getProductName() {
		return getProductInfo().getProductName();
	}

	/**
	 * Returns the image descriptors for the window image to use for this product.
	 *
	 * @return an array of the image descriptors for the window image, or
	 *         <code>null</code> if none
	 * @since 3.0
	 */
	public ImageDescriptor[] getWindowImages() {
		return getProductInfo().getWindowImages();
	}

	/**
	 * Returns an instance that describes this plugin's product (formerly "primary
	 * plugin").
	 *
	 * @return ProductInfo the product info for the receiver
	 */
	private ProductInfo getProductInfo() {
		if (productInfo == null) {
			productInfo = new ProductInfo(Platform.getProduct());
		}
		return productInfo;
	}

	@Override
	public void stop(BundleContext context) throws Exception {
		if (bundleListener != null) {
			context.removeBundleListener(bundleListener);
			bundleListener = null;
		}
		if (debugTracker != null) {
			debugTracker.close();
			debugTracker = null;
		}
		if (testableTracker != null) {
			testableTracker.close();
			testableTracker = null;
		}
		super.stop(context);
	}

	/**
	 * Return the new wizard registry.
	 *
	 * @return the new wizard registry
	 * @since 3.1
	 */
	public IWizardRegistry getNewWizardRegistry() {
		return e4Context.get(NewWizardRegistry.class);
	}

	/**
	 * Return the import wizard registry.
	 *
	 * @return the import wizard registry
	 * @since 3.1
	 */
	public IWizardRegistry getImportWizardRegistry() {
		return e4Context.get(ImportWizardRegistry.class);
	}

	/**
	 * Return the export wizard registry.
	 *
	 * @return the export wizard registry
	 * @since 3.1
	 */
	public IWizardRegistry getExportWizardRegistry() {
		return e4Context.get(ExportWizardRegistry.class);
	}

	/**
	 * FOR INTERNAL WORKBENCH USE ONLY.
	 *
	 * Returns the path to a location in the file system that can be used to
	 * persist/restore state between workbench invocations. If the location did not
	 * exist prior to this call it will be created. Returns <code>null</code> if no
	 * such location is available.
	 *
	 * @return path to a location in the file system where this plug-in can persist
	 *         data between sessions, or <code>null</code> if no such location is
	 *         available.
	 * @since 3.1
	 */
	public IPath getDataLocation() {
		try {
			return getStateLocation();
		} catch (IllegalStateException e) {
			// This occurs if -data=@none is explicitly specified, so return null
			return null;
		}
	}

	/* package */ void addBundleListener(BundleListener bundleListener) {
		bundleContext.addBundleListener(bundleListener);
	}

	/* package */ void removeBundleListener(BundleListener bundleListener) {
		bundleContext.removeBundleListener(bundleListener);
	}

	/* package */ int getBundleCount() {
		return bundleContext.getBundles().length;
	}

	/**
	 * @return the bundle listener for this plug-in
	 */
	private BundleListener getBundleListener() {
		if (bundleListener == null) {
			bundleListener = WorkbenchPlugin.this::bundleChanged;
		}
		return bundleListener;
	}

	private void bundleChanged(BundleEvent event) {
		int eventType = event.getType();
		// a bundle in the STARTING state generates 2 events, LAZY_ACTIVATION
		// when it enters STARTING and STARTING when it exists STARTING :-)
		synchronized (startingBundles) {
			switch (eventType) {
			case BundleEvent.STARTING:
				startingBundles.add(event.getBundle());
				break;
			case BundleEvent.STARTED:
			case BundleEvent.STOPPED:
				startingBundles.remove(event.getBundle());
				break;
			default:
				break;
			}
		}
	}

	public boolean isStarting(Bundle bundle) {
		synchronized (startingBundles) {
			return startingBundles.contains(bundle);
		}
	}

	/**
	 * Return whether or not the OSGi framework has specified the handle of a splash
	 * shell.
	 *
	 * @return whether or not the OSGi framework has specified the handle of a
	 *         splash shell
	 * @since 3.4
	 */
	public static boolean isSplashHandleSpecified() {
		return System.getProperty(PROP_SPLASH_HANDLE) != null;
	}

	/**
	 * Get the splash shell for this workbench instance, if any. This will find the
	 * splash created by the launcher (native) code and wrap it in a SWT shell. This
	 * may have the side effect of setting data on the provided {@link Display}.
	 *
	 * @param display the display to parent the shell on
	 *
	 * @return the splash shell or <code>null</code>
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws NumberFormatException
	 * @see Display#setData(String, Object)
	 * @since 3.4
	 */
	public static Shell getSplashShell(Display display)
			throws NumberFormatException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		Shell splashShell = (Shell) display.getData(DATA_SPLASH_SHELL);
		if (splashShell != null)
			return splashShell;

		String splashHandle = System.getProperty(PROP_SPLASH_HANDLE);
		if (splashHandle == null) {
			return null;
		}

		splashShell = Shell.internal_new(display, Long.parseLong(splashHandle));

		display.setData(DATA_SPLASH_SHELL, splashShell);
		return splashShell;
	}

	/**
	 * Removes any splash shell data set on the provided display and disposes the
	 * shell if necessary.
	 *
	 * @param display the display to parent the shell on
	 * @since 3.4
	 */
	public static void unsetSplashShell(Display display) {
		Shell splashShell = (Shell) display.getData(DATA_SPLASH_SHELL);
		if (splashShell != null) {
			if (!splashShell.isDisposed())
				splashShell.dispose();
			display.setData(DATA_SPLASH_SHELL, null);
		}

	}

	/**
	 * Initialized the workbench plug-in with the e4 context
	 *
	 * @param context the e4 context
	 */
	public void initializeContext(IEclipseContext context) {
		e4Context = context;
		e4Context.set(IPerspectiveRegistry.class.getName(), new ContextFunction() {

			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (perspRegistry == null) {
					perspRegistry = ContextInjectionFactory.make(PerspectiveRegistry.class, e4Context);
				}
				return perspRegistry;
			}
		});
		e4Context.set(IViewRegistry.class.getName(), new ContextFunction() {

			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (viewRegistry == null) {
					viewRegistry = ContextInjectionFactory.make(ViewRegistry.class, e4Context);
				}
				return viewRegistry;
			}
		});
		e4Context.set(ActionSetRegistry.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (actionSetRegistry == null) {
					actionSetRegistry = new ActionSetRegistry();
				}
				return actionSetRegistry;
			}
		});
		context.set(IDecoratorManager.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (decoratorManager == null) {
					decoratorManager = new DecoratorManager();
					decoratorManager.schedule();
				}
				return decoratorManager;
			}
		});
		context.set(ExportWizardRegistry.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				return ExportWizardRegistry.getInstance();
			}
		});
		context.set(ImportWizardRegistry.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				return ImportWizardRegistry.getInstance();
			}
		});
		context.set(IIntroRegistry.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (introRegistry == null) {
					introRegistry = new IntroRegistry();
				}
				return introRegistry;
			}
		});
		context.set(NewWizardRegistry.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				return NewWizardRegistry.getInstance();
			}
		});
		context.set(IWorkbenchOperationSupport.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (operationSupport == null) {
					operationSupport = new WorkbenchOperationSupport();
				}
				return operationSupport;
			}
		});
		context.set(PreferenceManager.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (preferenceManager == null) {
					preferenceManager = new WorkbenchPreferenceManager(PREFERENCE_PAGE_CATEGORY_SEPARATOR);

					// Get the pages from the registry
					PreferencePageRegistryReader registryReader = new PreferencePageRegistryReader(
							PlatformUI.getWorkbench());
					registryReader.loadFromRegistry(Platform.getExtensionRegistry());
					preferenceManager.addPages(registryReader.getTopLevelNodes());

				}
				return preferenceManager;
			}
		});
		context.set(ISharedImages.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (sharedImages == null) {
					sharedImages = new SharedImages();
				}
				return sharedImages;
			}
		});

		context.set(IThemeRegistry.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (themeRegistry == null) {
					themeRegistry = new ThemeRegistry();
					ThemeRegistryReader reader = new ThemeRegistryReader();
					reader.readThemes(Platform.getExtensionRegistry(), themeRegistry);
				}
				return themeRegistry;
			}
		});
		context.set(IWorkingSetManager.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (workingSetManager == null) {
					workingSetManager = new WorkingSetManager(bundleContext);
					workingSetManager.restoreState();
				}
				return workingSetManager;
			}
		});
		context.set(WorkingSetRegistry.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (workingSetRegistry == null) {
					workingSetRegistry = new WorkingSetRegistry();
					workingSetRegistry.load();
				}
				return workingSetRegistry;
			}
		});
		context.set(IEditorRegistry.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (editorRegistry == null) {
					editorRegistry = new EditorRegistry(Platform.getContentTypeManager());
				}
				return editorRegistry;
			}
		});
		context.set(EHelpService.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (helpService == null) {
					helpService = new HelpServiceImpl();
				}
				return helpService;
			}
		});
		context.set(ICommandHelpService.class.getName(), new ContextFunction() {
			@Override
			public Object compute(IEclipseContext context, String contextKey) {
				if (commandHelpService == null) {
					commandHelpService = ContextInjectionFactory.make(CommandHelpServiceImpl.class, e4Context);
				}
				return commandHelpService;
			}
		});
	}

	/*
	 * Return the debug options service, if available.
	 */
	public DebugOptions getDebugOptions() {
		if (bundleContext == null)
			return null;
		if (debugTracker == null) {
			debugTracker = new ServiceTracker(bundleContext, DebugOptions.class.getName(), null);
			debugTracker.open();
		}
		return (DebugOptions) debugTracker.getService();
	}

	/**
	 * Returns a {@link TestableObject} provided by a TestableObject service or
	 * <code>null</code> if a service implementation cannot be found. The
	 * TestableObject is used to hook tests into the application lifecycle.
	 * <p>
	 * It is recommended the testable object is obtained via service over
	 * {@link Workbench#getWorkbenchTestable()} to avoid the tests having a
	 * dependency on the Workbench.
	 * </p>
	 *
	 * @see PlatformUI#getTestableObject()
	 * @return TestableObject provided via service or <code>null</code>
	 */
	public TestableObject getTestableObject() {
		if (bundleContext == null)
			return null;
		if (testableTracker == null) {
			testableTracker = new ServiceTracker(bundleContext, TestableObject.class.getName(), null);
			testableTracker.open();
		}
		return (TestableObject) testableTracker.getService();
	}
}
