/*******************************************************************************
 * 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
 * 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
 *     Anton Kosyakov (Itemis AG) - Bug 438621 - [step filtering] Provide an extension point to enhance methods step filtering.
 *******************************************************************************/
package org.eclipse.debug.core;


import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.eclipse.core.resources.ISaveContext;
import org.eclipse.core.resources.ISaveParticipant;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IDisconnect;
import org.eclipse.debug.core.model.IDropToFrame;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStep;
import org.eclipse.debug.core.model.IStepFilter;
import org.eclipse.debug.core.model.IStepFilters;
import org.eclipse.debug.core.model.ISuspendResume;
import org.eclipse.debug.core.model.ITerminate;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.core.model.RuntimeProcess;
import org.eclipse.debug.internal.core.BreakpointManager;
import org.eclipse.debug.internal.core.DebugCoreMessages;
import org.eclipse.debug.internal.core.DebugOptions;
import org.eclipse.debug.internal.core.ExpressionManager;
import org.eclipse.debug.internal.core.IConfigurationElementConstants;
import org.eclipse.debug.internal.core.IInternalDebugCoreConstants;
import org.eclipse.debug.internal.core.LaunchManager;
import org.eclipse.debug.internal.core.LogicalStructureManager;
import org.eclipse.debug.internal.core.MemoryBlockManager;
import org.eclipse.debug.internal.core.Preferences;
import org.eclipse.debug.internal.core.StepFilterManager;
import org.eclipse.debug.internal.core.commands.CommandAdapterFactory;
import org.eclipse.debug.internal.core.groups.GroupMemberChangeListener;
import org.eclipse.debug.internal.core.sourcelookup.SourceLookupUtils;
import org.eclipse.osgi.service.environment.Constants;
import org.osgi.framework.BundleContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * There is one instance of the debug plug-in available from
 * <code>DebugPlugin.getDefault()</code>. The debug plug-in provides:
 * <ul>
 * <li>access to the breakpoint manager</li>
 * <li>access to the launch manager</li>
 * <li>access to the expression manager</li>
 * <li>access to the registered launcher extensions</li>
 * <li>access to the memory block manager</li>
 * <li>debug event notification</li>
 * <li>status handlers</li>
 * </ul>
 * @noinstantiate This class is not intended to be instantiated by clients.
 * @noextend This class is not intended to be sub-classed by clients.
 */
public class DebugPlugin extends Plugin {

	/**
	 * Unique identifier constant (value <code>"org.eclipse.debug.core"</code>)
	 * for the Debug Core plug-in.
	 */
	private static final String PI_DEBUG_CORE = "org.eclipse.debug.core"; //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"launchConfigurationTypes"</code>)
	 * for the launch configuration types extension point.
	 *
	 * @since 2.0
	 */
	public static final String EXTENSION_POINT_LAUNCH_CONFIGURATION_TYPES= "launchConfigurationTypes"; //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"launchConfigurationComparators"</code>)
	 * for the launch configuration comparators extension point.
	 *
	 * @since 2.0
	 */
	public static final String EXTENSION_POINT_LAUNCH_CONFIGURATION_COMPARATORS= "launchConfigurationComparators"; //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"breakpoints"</code>) for the
	 * breakpoints extension point.
	 *
	 * @since 2.0
	 */
	public static final String EXTENSION_POINT_BREAKPOINTS= "breakpoints";	 //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"statusHandlers"</code>) for the
	 * status handlers extension point.
	 *
	 * @since 2.0
	 */
	public static final String EXTENSION_POINT_STATUS_HANDLERS= "statusHandlers";	 //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"sourceLocators"</code>) for the
	 * source locators extension point.
	 *
	 * @since 2.0
	 */
	public static final String EXTENSION_POINT_SOURCE_LOCATORS= "sourceLocators";	 //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"launchModes"</code>) for the
	 * source modes extension point.
	 *
	 * @since 3.0
	 */
	public static final String EXTENSION_POINT_LAUNCH_MODES= "launchModes";	 //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"launchDelegates"</code>) for the
	 * launch delegates extension point.
	 *
	 * @since 3.0
	 */
	public static final String EXTENSION_POINT_LAUNCH_DELEGATES= "launchDelegates";	 //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"processFactories"</code>) for the
	 * process factories extension point.
	 *
	 * @since 3.0
	 */
	public static final String EXTENSION_POINT_PROCESS_FACTORIES = "processFactories"; //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"logicalStructureTypes"</code>) for the
	 * logical structure types extension point.
	 *
	 * @since 3.0
	 */
	public static final String EXTENSION_POINT_LOGICAL_STRUCTURE_TYPES = "logicalStructureTypes"; //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"logicalStructureProviders"</code>) for the
	 * logical structure types extension point.
	 *
	 * @since 3.1
	 */
	public static final String EXTENSION_POINT_LOGICAL_STRUCTURE_PROVIDERS = "logicalStructureProviders"; //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"sourceContainerTypes"</code>) for the
	 * source container types extension point.
	 *
	 * @since 3.0
	 */
	public static final String EXTENSION_POINT_SOURCE_CONTAINER_TYPES = "sourceContainerTypes";	 //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"sourcePathComputers"</code>) for the
	 * source path computers extension point.
	 *
	 * @since 3.0
	 */
	public static final String EXTENSION_POINT_SOURCE_PATH_COMPUTERS = "sourcePathComputers"; //$NON-NLS-1$

	/**
	 * Simple identifier constant for the launch options extension point
	 *
	 * @since 3.3
	 */
	public static final String EXTENSION_POINT_LAUNCH_OPTIONS = "launchOptions"; //$NON-NLS-1$

	/**
	 * Simple identifier constant for the breakpoint import participant extension point
	 *
	 * @since 3.5
	 */
	public static final String EXTENSION_POINT_BREAKPOINT_IMPORT_PARTICIPANTS = "breakpointImportParticipants"; //$NON-NLS-1$

	/**
	 * Simple identifier constant (value <code>"stepFilters"</code>) for the
	 * step filters extension point.
	 *
	 * @since 3.10
	 */
	public static final String EXTENSION_POINT_STEP_FILTERS = "stepFilters"; //$NON-NLS-1$

	/**
	 * Status code indicating an unexpected error.
	 *
	 * @since 3.4
	 */
	public static final int ERROR = 125;

	/**
	 * Status code indicating an unexpected internal error.  Internal errors
	 * should never be displayed to the user in dialogs or status text.
	 * Internal error messages are not translated.
	 */
	public static final int INTERNAL_ERROR = 120;

	/**
	 * Status code indicating that the Eclipse runtime does not support
	 * launching a program with a working directory. This feature is only
	 * available if Eclipse is run on a 1.3 runtime or higher.
	 * <p>
	 * A status handler may be registered for this error condition,
	 * and should return a <code>Boolean</code> indicating whether the program
	 * should be re-launched with the default working directory.
	 * </p>
	 */
	public static final int ERR_WORKING_DIRECTORY_NOT_SUPPORTED = 115;

	/**
	 * The launch configuration attribute that designates the process factory ID
	 * for the process factory to be used when creating a new process as a result of launching
	 * the launch configuration.
	 * @since 3.0
	 */
	public static final String ATTR_PROCESS_FACTORY_ID = "process_factory_id"; //$NON-NLS-1$

	/**
	 * The launch attribute that designates whether or not it's associated
	 * launch should capture output. Value is a string representing a boolean -
	 * <code>true</code> or <code>false</code>. When unspecified, the default
	 * value is considered <code>true</code>.
	 *
	 * @since 3.1
	 */
	public static final String ATTR_CAPTURE_OUTPUT = PI_DEBUG_CORE + ".capture_output"; //$NON-NLS-1$

	/**
	 * The launch attribute that stores the time stamp of when a launch configuration was
	 * launched. Value is {@link Long#toString(long)} of {@link System#currentTimeMillis()}.
	 *
	 * @since 3.6
	 */
	public static final String ATTR_LAUNCH_TIMESTAMP = PI_DEBUG_CORE + ".launch.timestamp";  //$NON-NLS-1$

	/**
	 * The launch attribute that stores the time stamp of when a launch configuration was
	 * launched. Value is {@link Long#toString(long)} of {@link System#currentTimeMillis()}.
	 *
	 * @since 3.15
	 */
	public static final String ATTR_TERMINATE_TIMESTAMP = PI_DEBUG_CORE + ".terminate.timestamp"; //$NON-NLS-1$

	/**
	 * This launch attribute designates the encoding to be used by the console
	 * associated with the launch.
	 * <p>
	 * For release 3.3, the system encoding is used when unspecified. Since 3.4,
	 * the inherited encoding is used when unspecified. See {@link ILaunchManager} for a
	 * description in <code>getEncoding(ILaunchConfiguration)</code>.
	 * </p>
	 * <p>
	 * Value of this constant is the same as the value of the old
	 * <code>IDebugUIConstants.ATTR_CONSOLE_ENCODING</code> constant for backward
	 * compatibility.
	 * </p>
	 * @since 3.3
	 * @see #ATTR_FORCE_SYSTEM_CONSOLE_ENCODING
	 */
	public static final String ATTR_CONSOLE_ENCODING = "org.eclipse.debug.ui.ATTR_CONSOLE_ENCODING"; //$NON-NLS-1$

	/**
	 * This launch attribute forces system encoding to be used by the
	 * console associated with the launch, independently on other attributes.
	 *
	 * Since 3.20, this flag is used by {@link ILaunchManager} to define
	 * encoding in <code>getEncoding(ILaunchConfiguration)</code>.
	 *
	 * @since 3.20
	 */
	public static final String ATTR_FORCE_SYSTEM_CONSOLE_ENCODING = PI_DEBUG_CORE + ".ATTR_FORCE_SYSTEM_CONSOLE_ENCODING"; //$NON-NLS-1$

	/**
	 * Launch configuration attribute - a boolean value indicating whether a
	 * configuration should be launched with merged error and standard output.
	 * Merging output can ensure the process output appears in console in same
	 * order as the process produce it. On the other hand the error output can
	 * not be colored different from standard output anymore. Default value is
	 * <code>false</code>.
	 *
	 * @since 3.14
	 */
	public static final String ATTR_MERGE_OUTPUT = PI_DEBUG_CORE + ".ATTR_MERGE_OUTPUT"; //$NON-NLS-1$

	/**
	 * Boolean preference key (value
	 * <code>org.eclipse.debug.core.PREF_DELETE_CONFIGS_ON_PROJECT_DELETE</code>)
	 * that controls whether to delete associated configurations when a project
	 * is deleted. Default value is <code>false</code>.
	 *
	 * @since 3.7
	 */
	public static final String PREF_DELETE_CONFIGS_ON_PROJECT_DELETE = PI_DEBUG_CORE + ".PREF_DELETE_CONFIGS_ON_PROJECT_DELETE"; //$NON-NLS-1$

	/**
	 * Deleted breakpoint marker attribute (value
	 * <code>"org.eclipse.debug.core.breakpointIsDeleted"</code>). The attribute is a
	 * <code>boolean</code> corresponding to the deleted state of a breakpoint.
	 *
	 * @see org.eclipse.core.resources.IMarker#getAttribute(String, boolean)
	 * @since 3.7
	 */
	public static final String ATTR_BREAKPOINT_IS_DELETED= PI_DEBUG_CORE + ".breakpointIsDeleted"; //$NON-NLS-1$

	/**
	 * Attribute key for the environment used when an {@link IProcess} was run
	 * @see IProcess
	 * @since 3.8
	 */
	public static final String ATTR_ENVIRONMENT = PI_DEBUG_CORE + ".ATTR_ENVIRONMENT"; //$NON-NLS-1$

	/**
	 * Attribute key for the path of the working directory for an {@link IProcess}
	 *
	 * @see IProcess
	 * @since 3.8
	 */
	public static final String ATTR_WORKING_DIRECTORY = PI_DEBUG_CORE + ".ATTR_WORKING_DIRECTORY"; //$NON-NLS-1$

	/**
	 * Attribute key for path of the executable that launched an {@link IProcess}
	 *
	 * @see IProcess
	 * @since 3.8
	 */
	public static final String ATTR_PATH = PI_DEBUG_CORE + ".ATTR_PATH"; //$NON-NLS-1$

	/**
	 * Launch configuration attribute that designates whether or not the
	 * descendants of the {@link IProcess} associated to a launch of this
	 * configuration should be terminated if the main-process is terminated. The
	 * descendants (also called child- or sub-processes) of a operating system
	 * process are the processes started by that process.
	 *
	 * Value is a string representing a boolean - <code>true</code> or
	 * <code>false</code>. When unspecified, the default value is considered
	 * <code>true</code>.
	 *
	 * @since 3.18
	 */
	public static final String ATTR_TERMINATE_DESCENDANTS = PI_DEBUG_CORE + ".TERMINATE_DESCENDANTS"; //$NON-NLS-1$

	/**
	 * The singleton debug plug-in instance.
	 */
	private static DebugPlugin fgDebugPlugin= null;

	/**
	 * The singleton breakpoint manager.
	 */
	private BreakpointManager fBreakpointManager;

	/**
	 * The singleton expression manager.
	 */
	private ExpressionManager fExpressionManager;

	/**
	 * The singleton launch manager.
	 */
	private LaunchManager fLaunchManager;

	/**
	 * The singleton memory block manager.
	 * @since 3.1
	 */
	private MemoryBlockManager fMemoryBlockManager;

	/**
	 * The collection of debug event listeners.
	 */
	private ListenerList<IDebugEventSetListener> fEventListeners = new ListenerList<>();

	/**
	 * Event filters, or <code>null</code> if none.
	 */
	private ListenerList<IDebugEventFilter> fEventFilters = new ListenerList<>();

	/**
	 * Whether this plug-in is in the process of shutting
	 * down.
	 */
	private boolean fShuttingDown= false;

	/**
	 * Table of status handlers. Keys are {plug-in identifier, status code}
	 * pairs, and values are associated <code>IConfigurationElement</code>s.
	 */
	private HashMap<StatusHandlerKey, IConfigurationElement> fStatusHandlers = null;

	/**
	 * Map of process factories. Keys are process factory IDs
	 * and values are associated <code>IConfigurationElement</code>s.
	 * @since 3.0
	 */
	private HashMap<String, IConfigurationElement> fProcessFactories = null;

	/**
	 * Mode constants for the event notifier
	 */
	private static final int NOTIFY_FILTERS = 0;
	private static final int NOTIFY_EVENTS = 1;

	/**
	 * Queue of debug events to fire to listeners and asynchronous runnables to execute
	 * in the order received.
	 *
	 * @since 3.1
	 */
	private List<Object> fEventQueue = new ArrayList<>();

	/**
	 * Job to fire events to listeners.
	 * @since 3.1
	 */
	private EventDispatchJob fEventDispatchJob = new EventDispatchJob();

	/**
	 * Event dispatch job. Processes event queue of debug events and runnables.
	 *
	 * @since 3.1
	 */
	class EventDispatchJob extends Job {

		EventNotifier fNotifier = new EventNotifier();
		AsynchRunner fRunner = new AsynchRunner();

		/**
		 * Creates a new event dispatch job.
		 */
		public EventDispatchJob() {
			super(DebugCoreMessages.DebugPlugin_1);
			setPriority(Job.INTERACTIVE);
			setSystem(true);
		}

		@Override
		protected IStatus run(IProgressMonitor monitor) {

			while (!fEventQueue.isEmpty()) {
				Object next = null;
				synchronized (fEventQueue) {
					if (!fEventQueue.isEmpty()) {
						next = fEventQueue.remove(0);
					}
				}
				if (next instanceof Runnable) {
					fRunner.async((Runnable) next);
				} else if (next != null) {
					fNotifier.dispatch((DebugEvent[]) next);
				}
			}
			return Status.OK_STATUS;
		}

		@Override
		public boolean shouldRun() {
			return shouldSchedule();
		}

		@Override
		public boolean shouldSchedule() {
			return !(isShuttingDown() || fEventListeners.isEmpty());
		}

	}

	/**
	 * Returns the singleton instance of the debug plug-in.
	 *
	 * @return the debug plug-in
	 */
	public static DebugPlugin getDefault() {
		return fgDebugPlugin;
	}

	/**
	 * Sets the singleton instance of the debug plug-in.
	 *
	 * @param plugin the debug plug-in, or <code>null</code>
	 *  when shutting down
	 */
	private static void setDefault(DebugPlugin plugin) {
		fgDebugPlugin = plugin;
	}

	/**
	 * Convenience method which returns the unique identifier of this plug-in.
	 *
	 * @return debug plug-in identifier
	 */
	public static String getUniqueIdentifier() {
		return PI_DEBUG_CORE;
	}

	/**
	 * Constructs the debug plug-in.
	 * <p>
	 * An instance of this plug-in runtime class is automatically created
	 * when the facilities provided by this plug-in are required.
	 * <b>Clients must never explicitly instantiate a plug-in runtime class.</b>
	 * </p>
	 */
	public DebugPlugin() {
		super();
		setDefault(this);
	}

	/**
	 * Adds the given listener to the collection of registered debug
	 * event listeners. Has no effect if an identical listener is already
	 * registered.
	 *
	 * @param listener the listener to add
	 * @since 2.0
	 */
	public void addDebugEventListener(IDebugEventSetListener listener) {
		fEventListeners.add(listener);
	}

	/**
	 * Notifies all registered debug event set listeners of the given
	 * debug events. Events which are filtered by a registered debug event
	 * filter are not fired.
	 *
	 * @param events array of debug events to fire
	 * @see IDebugEventFilter
	 * @see IDebugEventSetListener
	 * @since 2.0
	 */
	public void fireDebugEventSet(DebugEvent[] events) {
		if (isShuttingDown() || events == null || fEventListeners.isEmpty()) {
			return;
		}
		synchronized (fEventQueue) {
			fEventQueue.add(events);
		}
		fEventDispatchJob.schedule();
	}

	/**
	 * Asynchronously executes the given runnable in a separate
	 * thread, after debug event dispatch has completed. If debug
	 * events are not currently being dispatched, the runnable is
	 * scheduled to run in a separate thread immediately.
	 *
	 * @param r runnable to execute asynchronously
	 * @since 2.1
	 */
	public void asyncExec(Runnable r) {
		synchronized (fEventQueue) {
			fEventQueue.add(r);
		}
		fEventDispatchJob.schedule();
	}

	/**
	 * Returns the breakpoint manager.
	 *
	 * @return the breakpoint manager
	 * @see IBreakpointManager
	 */
	public synchronized IBreakpointManager getBreakpointManager() {
		if (fBreakpointManager == null) {
			fBreakpointManager = new BreakpointManager();
		}
		return fBreakpointManager;
	}

	/**
	 * Returns the launch manager.
	 *
	 * @return the launch manager
	 * @see ILaunchManager
	 */
	public synchronized ILaunchManager getLaunchManager() {
		if (fLaunchManager == null) {
			fLaunchManager = new LaunchManager();
			fLaunchManager.getAllLaunchConfigurations();
			// monitor launch configuration renames for launch groups
			fLaunchManager.addLaunchConfigurationListener(new GroupMemberChangeListener());
		}
		return fLaunchManager;
	}

	/**
	 * Returns the memory block manager.
	 * @return the memory block manager.
	 * @see IMemoryBlockManager
	 * @since 3.1
	 */
	public synchronized IMemoryBlockManager getMemoryBlockManager(){
		if (fMemoryBlockManager == null) {
			fMemoryBlockManager = new MemoryBlockManager();
		}
		return fMemoryBlockManager;
	}

	/**
	 * Returns the status handler registered for the given
	 * status, or <code>null</code> if none.
	 *
	 * @param status status for which a status handler has been requested
	 * @return the status handler registered for the given
	 *  status, or <code>null</code> if none
	 * @since 2.0
	 */
	public IStatusHandler getStatusHandler(IStatus status) {
		boolean enabled = Platform.getPreferencesService().getBoolean(DebugPlugin.getUniqueIdentifier(), IInternalDebugCoreConstants.PREF_ENABLE_STATUS_HANDLERS, true, null);
		if (!enabled) {
			return null;
		}
		StatusHandlerKey key = new StatusHandlerKey(status.getPlugin(), status.getCode());
		if (fStatusHandlers == null) {
			initializeStatusHandlers();
		}
		IConfigurationElement config = fStatusHandlers.get(key);
		if (config != null) {
			try {
				Object handler = config.createExecutableExtension(IConfigurationElementConstants.CLASS);
				if (handler instanceof IStatusHandler) {
					return (IStatusHandler)handler;
				}
				invalidStatusHandler(null, MessageFormat.format("Registered status handler {0} does not implement required interface IStatusHandler.", new Object[] { config.getDeclaringExtension().getUniqueIdentifier() })); //$NON-NLS-1$
			} catch (CoreException e) {
				log(e);
			}
		}
		return null;
	}

	/**
	 * Returns the expression manager.
	 *
	 * @return the expression manager
	 * @see IExpressionManager
	 * @since 2.0
	 */
	public synchronized IExpressionManager getExpressionManager() {
		if (fExpressionManager == null) {
			fExpressionManager = new ExpressionManager();
		}
		return fExpressionManager;
	}

	/**
	 * Removes the given listener from the collection of registered debug
	 * event listeners. Has no effect if an identical listener is not already
	 * registered.
	 *
	 * @param listener the listener to remove
	 * @since 2.0
	 */
	public void removeDebugEventListener(IDebugEventSetListener listener) {
		fEventListeners.remove(listener);
	}

	@Override
	public void stop(BundleContext context) throws Exception {
		try {
			setShuttingDown(true);

			if (fLaunchManager != null) {
				fLaunchManager.shutdown();
			}
			if (fBreakpointManager != null) {
				fBreakpointManager.shutdown();
			}
			if (fMemoryBlockManager != null)  {
				fMemoryBlockManager.shutdown();
			}

			fEventListeners.clear();
			fEventFilters.clear();

			SourceLookupUtils.shutdown();
			Preferences.savePreferences(DebugPlugin.getUniqueIdentifier());
			ResourcesPlugin.getWorkspace().removeSaveParticipant(getUniqueIdentifier());
		} finally {
			super.stop(context);
			setDefault(null);
		}
	}

	@Override
	public void start(BundleContext context) throws Exception {
		super.start(context);
		new DebugOptions(context);
		ResourcesPlugin.getWorkspace().addSaveParticipant(getUniqueIdentifier(),
				new ISaveParticipant() {
					@Override
					public void saving(ISaveContext saveContext) throws CoreException {
						if (fExpressionManager != null) {
							fExpressionManager.storeWatchExpressions();
						}
						Preferences.savePreferences(DebugPlugin.getUniqueIdentifier());
					}
					@Override
					public void rollback(ISaveContext saveContext) {}
					@Override
					public void prepareToSave(ISaveContext saveContext) throws CoreException {}
					@Override
					public void doneSaving(ISaveContext saveContext) {}
				});
		//command adapters
		IAdapterManager manager= Platform.getAdapterManager();
		CommandAdapterFactory actionFactory = new CommandAdapterFactory();
		manager.registerAdapters(actionFactory, IDisconnect.class);
		manager.registerAdapters(actionFactory, IDropToFrame.class);
		manager.registerAdapters(actionFactory, IStep.class);
		manager.registerAdapters(actionFactory, IStepFilters.class);
		manager.registerAdapters(actionFactory, ISuspendResume.class);
		manager.registerAdapters(actionFactory, ITerminate.class);
		manager.registerAdapters(actionFactory, ILaunch.class);
		manager.registerAdapters(actionFactory, IProcess.class);
		manager.registerAdapters(actionFactory, IDebugElement.class);
	}

	/**
	 * Creates and returns a new process representing the given
	 * <code>java.lang.Process</code>. A streams proxy is created
	 * for the I/O streams in the system process. The process
	 * is added to the given launch.
	 * <p>
	 * If the launch configuration associated with the given launch
	 * specifies a process factory, it will be used to instantiate
	 * the new process.
	 * </p>
	 * @param launch the launch the process is contained in
	 * @param process the system process to wrap
	 * @param label the label assigned to the process
	 * @return the process
	 * @see IProcess
	 * @see IProcessFactory
	 */
	public static IProcess newProcess(ILaunch launch, Process process, String label) {
		return newProcess(launch, process, label, null);
	}

	/**
	 * Creates and returns a new process representing the given
	 * <code>java.lang.Process</code>. A streams proxy is created
	 * for the I/O streams in the system process. The process
	 * is added to the given launch, and the process is initialized
	 * with the given attribute map.
	 * <p>
	 * If the launch configuration associated with the given launch
	 * specifies a process factory, it will be used to instantiate
	 * the new process.
	 * </p>
	 * @param launch the launch the process is contained in
	 * @param process the system process to wrap
	 * @param label the label assigned to the process
	 * @param attributes initial values for the attribute map
	 * @return the process <code>null</code> can be returned if errors occur dealing with the process factory
	 * designated to create the process.
	 * @see IProcess
	 * @see IProcessFactory
	 * @since 2.1
	 */
	public static IProcess newProcess(ILaunch launch, Process process, String label, Map<String, String> attributes) {
		ILaunchConfiguration config= launch.getLaunchConfiguration();
		String processFactoryID= null;
		if (config != null) {
			try {
				processFactoryID= config.getAttribute(ATTR_PROCESS_FACTORY_ID, (String)null);
			} catch (CoreException e) {
			}
		}
		if (processFactoryID != null) {
			DebugPlugin plugin= DebugPlugin.getDefault();
			if (plugin.fProcessFactories == null) {
				plugin.initializeProcessFactories();
			}
			IConfigurationElement element= plugin.fProcessFactories.get(processFactoryID);
			if (element == null) {
				return null;
			}
			IProcessFactory processFactory= null;
			try {
				processFactory = (IProcessFactory)element.createExecutableExtension(IConfigurationElementConstants.CLASS);
			} catch (CoreException exception) {
				log(exception);
				return null;
			}
			return processFactory.newProcess(launch, process, label, attributes);
		}
		return new RuntimeProcess(launch, process, label, attributes);
	}

	/**
	 * Returns any logical structure types that have been contributed for the given
	 * value.
	 *
	 * @param value the value for which logical structure types have been requested
	 * @return logical structure types that have been contributed for the given
	 * value, possibly an empty collection
	 *
	 * @since 3.0
	 */
	public static ILogicalStructureType[] getLogicalStructureTypes(IValue value) {
		return LogicalStructureManager.getDefault().getLogicalStructureTypes(value);
	}

	/**
	 * Returns the default logical structure type among the given combination of
	 * logical structure types, or <code>null</code> if none. When the given combination
	 * of logical structure type is applicable for a value, the default logical structure
	 * type is used to display a value.
	 *
	 * @param types a combination of structures applicable to a value
	 * @return the default structure that should be used to display the value
	 * or <code>null</code> if none
	 *
	 * @since 3.1
	 */
	public static ILogicalStructureType getDefaultStructureType(ILogicalStructureType[] types) {
		return LogicalStructureManager.getDefault().getSelectedStructureType(types);
	}

	/**
	 * Sets the default logical structure type among the given combination of logical structure
	 * types. The logical structure types provided should all be applicable to a single
	 * value. Specifying <code>null</code> indicates there is no default logical structure
	 * for the given combination of types.
	 *
	 * @param types a combination of logical structure types applicable to a value
	 * @param def the default logical structure among the given combination of types
	 * or <code>null</code> if none
	 *
	 * @since 3.1
	 */
	public static void setDefaultStructureType(ILogicalStructureType[] types, ILogicalStructureType def) {
		LogicalStructureManager.getDefault().setEnabledType(types, def);
	}

	/**
	 * Convenience method that performs a runtime exec on the given command line
	 * in the context of the specified working directory, and returns the
	 * resulting process. If the current runtime does not support the
	 * specification of a working directory, the status handler for error code
	 * <code>ERR_WORKING_DIRECTORY_NOT_SUPPORTED</code> is queried to see if the
	 * exec should be re-executed without specifying a working directory.
	 *
	 * @param cmdLine the command line
	 * @param workingDirectory the working directory, or <code>null</code>
	 * @return the resulting process or <code>null</code> if the exec is
	 *  canceled
	 * @exception CoreException if the exec fails
	 * @see Runtime
	 *
	 * @since 2.1
	 */
	public static Process exec(String[] cmdLine, File workingDirectory) throws CoreException {
		return exec(cmdLine, workingDirectory, null);
	}

	/**
	 * Convenience method that performs a runtime exec on the given command line
	 * in the context of the specified working directory, and returns the
	 * resulting process. If the current runtime does not support the
	 * specification of a working directory, the status handler for error code
	 * <code>ERR_WORKING_DIRECTORY_NOT_SUPPORTED</code> is queried to see if the
	 * exec should be re-executed without specifying a working directory.
	 *
	 * @param cmdLine the command line
	 * @param workingDirectory the working directory, or <code>null</code>
	 * @param envp the environment variables set in the process, or <code>null</code>
	 * @return the resulting process or <code>null</code> if the exec is
	 *  canceled
	 * @exception CoreException if the exec fails
	 * @see Runtime
	 *
	 * @since 3.0
	 */
	public static Process exec(String[] cmdLine, File workingDirectory, String[] envp) throws CoreException {
		return exec(cmdLine, workingDirectory, envp, false);
	}

	/**
	 * Convenience method that performs a runtime exec on the given command line
	 * in the context of the specified working directory, and returns the
	 * resulting process. If the current runtime does not support the
	 * specification of a working directory, the status handler for error code
	 * <code>ERR_WORKING_DIRECTORY_NOT_SUPPORTED</code> is queried to see if the
	 * exec should be re-executed without specifying a working directory.
	 *
	 * @param cmdLine the command line
	 * @param workingDirectory the working directory, or <code>null</code>
	 * @param envp the environment variables set in the process, or
	 *            <code>null</code>
	 * @param mergeOutput if <code>true</code> the error stream will be merged
	 *            with standard output stream and both can be read through the
	 *            same output stream
	 * @return the resulting process or <code>null</code> if the exec is
	 *         canceled
	 * @exception CoreException if the exec fails
	 * @see Runtime
	 *
	 * @since 3.14
	 */
	public static Process exec(String[] cmdLine, File workingDirectory, String[] envp, boolean mergeOutput) throws CoreException {
		Process p = null;
		try {
			// starting with and without merged output could be done with the
			// same process builder approach but since the handling of
			// environment variables is slightly different between
			// ProcessBuilder and Runtime.exec only the new option uses process
			// builder to not break existing caller of this method
			if (mergeOutput) {
				ProcessBuilder pb = new ProcessBuilder(cmdLine);
				pb.directory(workingDirectory);
				pb.redirectErrorStream(mergeOutput);
				if (envp != null) {
					Map<String, String> env = pb.environment();
					env.clear();
					for (String e : envp) {
						int index = e.indexOf('=');
						if (index != -1) {
							env.put(e.substring(0, index), e.substring(index + 1));
						}
					}
				}
				p = pb.start();
			} else if (workingDirectory == null) {
				p = Runtime.getRuntime().exec(cmdLine, envp);
			} else {
				p = Runtime.getRuntime().exec(cmdLine, envp, workingDirectory);
			}
		} catch (IOException e) {
			Status status = new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, DebugCoreMessages.DebugPlugin_0, e);
			throw new CoreException(status);
		} catch (NoSuchMethodError e) {
			//attempting launches on 1.2.* - no ability to set working directory
			IStatus status = new Status(IStatus.ERROR, getUniqueIdentifier(), ERR_WORKING_DIRECTORY_NOT_SUPPORTED, DebugCoreMessages.DebugPlugin_Eclipse_runtime_does_not_support_working_directory_2, e);
			IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(status);

			if (handler != null) {
				Object result = handler.handleStatus(status, null);
				if (result instanceof Boolean && ((Boolean) result).booleanValue()) {
					p = exec(cmdLine, null);
				}
			}
		}
		return p;
	}

	/**
	 * Returns whether this plug-in is in the process of
	 * being shutdown.
	 *
	 * @return whether this plug-in is in the process of
	 *  being shutdown
	 */
	private boolean isShuttingDown() {
		return fShuttingDown;
	}

	/**
	 * Sets whether this plug-in is in the process of
	 * being shutdown.
	 *
	 * @param value whether this plug-in is in the process of
	 *  being shutdown
	 */
	private void setShuttingDown(boolean value) {
		fShuttingDown = value;
	}


	/**
	 * Adds the given debug event filter to the registered
	 * event filters. Has no effect if an identical filter
	 * is already registered.
	 *
	 * @param filter debug event filter
	 * @since 2.0
	 */
	public void addDebugEventFilter(IDebugEventFilter filter) {
		fEventFilters.add(filter);
	}

	/**
	 * Removes the given debug event filter from the registered
	 * event filters. Has no effect if an identical filter
	 * is not already registered.
	 *
	 * @param filter debug event filter
	 * @since 2.0
	 */
	public void removeDebugEventFilter(IDebugEventFilter filter) {
		fEventFilters.remove(filter);
	}

	/**
	 * Logs the given message if in debug mode.
	 *
	 * @param message the message to log
	 * @since 2.0
	 */
	public static void logDebugMessage(String message) {
		if (getDefault().isDebugging()) {
			// this message is intentionally not externalized, as an exception may
			// be due to the resource bundle itself
			log(new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, MessageFormat.format(DebugCoreMessages.DebugPlugin_2, new Object[] { message }), null));
		}
	}

	/**
	 * Logs the given message with this plug-in's log and the given
	 * throwable or <code>null</code> if none.
	 * @param message the message to log
	 * @param throwable the exception that occurred or <code>null</code> if none
	 */
	public static void logMessage(String message, Throwable throwable) {
		log(new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, message, throwable));
	}

	/**
	 * Logs the specified status with this plug-in's log.
	 *
	 * @param status status to log
	 * @since 2.0
	 */
	public static void log(IStatus status) {
		getDefault().getLog().log(status);
	}

	/**
	 * Logs the specified throwable with this plug-in's log.
	 *
	 * @param t throwable to log
	 * @since 2.0
	 */
	public static void log(Throwable t) {
		IStatus status= new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, DebugCoreMessages.DebugPlugin_3, t);
		log(status);
	}

	/**
	 * Register status handlers.
	 *
	 */
	private void initializeStatusHandlers() {
		IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(DebugPlugin.PI_DEBUG_CORE, EXTENSION_POINT_STATUS_HANDLERS);
		IConfigurationElement[] infos= extensionPoint.getConfigurationElements();
		fStatusHandlers = new HashMap<>(infos.length);
		for (IConfigurationElement configurationElement : infos) {
			String id = configurationElement.getAttribute("plugin"); //$NON-NLS-1$
			String code = configurationElement.getAttribute("code"); //$NON-NLS-1$

			if (id != null && code != null) {
				try {
					StatusHandlerKey key = new StatusHandlerKey(id, Integer.parseInt(code));
					fStatusHandlers.put(key, configurationElement);
				} catch (NumberFormatException e) {
					// invalid status handler
					invalidStatusHandler(e, configurationElement.getAttribute("id")); //$NON-NLS-1$
				}
			} else {
				// invalid status handler
				invalidStatusHandler(null, configurationElement.getAttribute("id")); //$NON-NLS-1$
			}
		}
	}

	/**
	 * Register process factories.
	 *
	 */
	private void initializeProcessFactories() {
		IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(DebugPlugin.PI_DEBUG_CORE, EXTENSION_POINT_PROCESS_FACTORIES);
		IConfigurationElement[] infos= extensionPoint.getConfigurationElements();
		fProcessFactories = new HashMap<>(infos.length);
		for (IConfigurationElement configurationElement : infos) {
			String id = configurationElement.getAttribute("id"); //$NON-NLS-1$
			String clss = configurationElement.getAttribute("class"); //$NON-NLS-1$
			if (id != null && clss != null) {
				fProcessFactories.put(id, configurationElement);
			} else {
				// invalid process factory
				String badDefiner = configurationElement.getContributor().getName();
				log(new Status(IStatus.ERROR, DebugPlugin.PI_DEBUG_CORE, ERROR, MessageFormat.format(DebugCoreMessages.DebugPlugin_4, new Object[] {
					badDefiner, id }), null));
			}
		}
	}

	private void invalidStatusHandler(Exception e, String id) {
		log(new Status(IStatus.ERROR, DebugPlugin.PI_DEBUG_CORE, ERROR, MessageFormat.format(DebugCoreMessages.DebugPlugin_5, new Object[] { id }), e));
	}

	/**
	 * Key for status handler extensions - a plug-in identifier/code pair
	 */
	class StatusHandlerKey {

		String fPluginId;
		int fCode;

		StatusHandlerKey(String pluginId, int code) {
			fPluginId = pluginId;
			fCode = code;
		}

		@Override
		public int hashCode() {
			return fPluginId.hashCode() + fCode;
		}

		@Override
		public boolean equals(Object obj) {
			if (obj instanceof StatusHandlerKey) {
				StatusHandlerKey s = (StatusHandlerKey)obj;
				return fCode == s.fCode && fPluginId.equals(s.fPluginId);
			}
			return false;
		}
	}

	/**
	 * Executes runnables after event dispatch is complete.
	 *
	 * @since 3.0
	 */
	class AsynchRunner implements ISafeRunnable {

		private Runnable fRunnable = null;

		void async(Runnable runnable) {
			fRunnable = runnable;
			SafeRunner.run(this);
			fRunnable = null;

		}

		@Override
		public void handleException(Throwable exception) {
			IStatus status = new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, DebugCoreMessages.DebugPlugin_6, exception);
			log(status);
		}

		@Override
		public void run() throws Exception {
			fRunnable.run();
		}

	}

	/**
	 * Filters and dispatches events in a safe runnable to handle any
	 * exceptions.
	 */
	class EventNotifier implements ISafeRunnable {

		private DebugEvent[] fEvents;
		private IDebugEventSetListener fListener;
		private IDebugEventFilter fFilter;
		private int fMode;

		@Override
		public void handleException(Throwable exception) {
			switch (fMode) {
				case NOTIFY_FILTERS:
					IStatus status = new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, DebugCoreMessages.DebugPlugin_7, exception);
					log(status);
					break;
				case NOTIFY_EVENTS:
					status = new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, DebugCoreMessages.DebugPlugin_8, exception);
					log(status);
					break;
				default:
					break;
			}
		}

		@Override
		public void run() throws Exception {
			switch (fMode) {
				case NOTIFY_FILTERS:
					fEvents = fFilter.filterDebugEvents(fEvents);
					break;
				case NOTIFY_EVENTS:
					fListener.handleDebugEvents(fEvents);
					break;
				default:
					break;
			}
		}

		/**
		 * Filter and dispatch the given events. If an exception occurs in one
		 * listener, events are still fired to subsequent listeners.
		 *
		 * @param events debug events
		 */
		void dispatch(DebugEvent[] events) {
			fEvents = events;
			if (!fEventFilters.isEmpty()) {
				fMode = NOTIFY_FILTERS;
				for (IDebugEventFilter iDebugEventFilter : fEventFilters) {
					fFilter = iDebugEventFilter;
					SafeRunner.run(this);
					if (fEvents == null || fEvents.length == 0) {
						return;
					}
				}
			}

			fMode = NOTIFY_EVENTS;
			if (DebugOptions.DEBUG_EVENTS) {
				for (DebugEvent event : fEvents) {
					DebugOptions.trace(event.toString());
				}
			}
			for (IDebugEventSetListener iDebugEventSetListener : fEventListeners) {
				fListener = iDebugEventSetListener;
				SafeRunner.run(this);
			}
			fEvents = null;
			fFilter = null;
			fListener = null;
		}

	}

	/**
	 * Creates and returns a new XML document.
	 *
	 * @return a new XML document
	 * @throws CoreException if unable to create a new document
	 * @since 3.0
	 */
	public static Document newDocument()throws CoreException {
		try {
			return LaunchManager.getDocument();
		} catch (ParserConfigurationException e) {
			abort("Unable to create new XML document.", e);  //$NON-NLS-1$
		}
		return null;
	}

	/**
	 * Serializes the given XML document into a string.
	 *
	 * @param document XML document to serialize
	 * @return a string representing the given document
	 * @throws CoreException if unable to serialize the document
	 * @since 3.0
	 */
	public static String serializeDocument(Document document) throws CoreException {
		try {
			return LaunchManager.serializeDocument(document);
		} catch (IOException | TransformerException e) {
			abort("Unable to serialize XML document.", e);  //$NON-NLS-1$
		}
		return null;
	}

	/**
	 * Parses the given string representing an XML document, returning its
	 * root element.
	 *
	 * @param document XML document as a string
	 * @return the document's root element
	 * @throws CoreException if unable to parse the document
	 * @since 3.0
	 */
	public static Element parseDocument(String document) throws CoreException {
		Element root = null;
		try{
			@SuppressWarnings("restriction")
			DocumentBuilder parser = org.eclipse.core.internal.runtime.XmlProcessorFactory.createDocumentBuilderWithErrorOnDOCTYPE();
			parser.setErrorHandler(new DefaultHandler());
			try (InputStream stream = new ByteArrayInputStream(document.getBytes(StandardCharsets.UTF_8))) {
				root = parser.parse(stream).getDocumentElement();
			}
		} catch (ParserConfigurationException e) {
			abort("Unable to parse XML document.", e);  //$NON-NLS-1$
		} catch (FactoryConfigurationError e) {
			abort("Unable to parse XML document.", e);  //$NON-NLS-1$
		} catch (SAXException e) {
			abort("Unable to parse XML document.", e);  //$NON-NLS-1$
		} catch (IOException e) {
			abort("Unable to parse XML document.", e);  //$NON-NLS-1$
		}
		return root;
	}

	/**
	 * Throws an exception with the given message and underlying exception.
	 *
	 * @param message error message
	 * @param exception underlying exception, or <code>null</code>
	 * @throws CoreException if a problem is encountered
	 */
	private static void abort(String message, Throwable exception) throws CoreException {
		IStatus status = new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(), DebugPlugin.ERROR, message, exception);
		throw new CoreException(status);
	}

	private static String[] parseArgumentsWindows(String args, boolean split) {
		// see http://msdn.microsoft.com/en-us/library/a1y7w461.aspx
		List<String> result = new ArrayList<>();

		final int DEFAULT= 0;
		final int ARG= 1;
		final int IN_DOUBLE_QUOTE= 2;

		int state= DEFAULT;
		int backslashes= 0;
		StringBuilder buf= new StringBuilder();
		int len= args.length();
		for (int i= 0; i < len; i++) {
			char ch= args.charAt(i);
			if (ch == '\\') {
				backslashes++;
				continue;
			} else if (backslashes != 0) {
				if (ch == '"') {
					for (; backslashes >= 2; backslashes-= 2) {
						buf.append('\\');
						if (split) {
							buf.append('\\');
						}
					}
					if (backslashes == 1) {
						if (state == DEFAULT) {
							state= ARG;
						}
						if (split) {
							buf.append('\\');
						}
						buf.append('"');
						backslashes= 0;
						continue;
					} // else fall through to switch
				} else {
					// false alarm, treat passed backslashes literally...
					if (state == DEFAULT) {
						state= ARG;
					}
					for (; backslashes > 0; backslashes--) {
						buf.append('\\');
					}
					// fall through to switch
				}
			}
			if (Character.isWhitespace(ch)) {
				if (state == DEFAULT) {
					// skip
					continue;
				} else if (state == ARG) {
					state= DEFAULT;
					result.add(buf.toString());
					buf.setLength(0);
					continue;
				}
			}
			switch (state) {
				case DEFAULT:
				case ARG:
					if (ch == '"') {
						state= IN_DOUBLE_QUOTE;
						if (split) {
							buf.append(ch);
						}
					} else {
						state= ARG;
						buf.append(ch);
					}
					break;

				case IN_DOUBLE_QUOTE:
					if (ch == '"') {
						if (i + 1 < len && args.charAt(i + 1) == '"') {
							/* Undocumented feature in Windows:
							 * Two consecutive double quotes inside a double-quoted argument are interpreted as
							 * a single double quote.
							 */
							buf.append('"');
							i++;
							if (split) {
								buf.append(ch);
							}
						} else if (buf.length() == 0) {
							// empty string on Windows platform. Account for bug in constructor of JDK's java.lang.ProcessImpl.
							result.add("\"\""); //$NON-NLS-1$
							state= DEFAULT;
						} else {
							state= ARG;
							if (split) {
								buf.append(ch);
							}
						}
					} else {
						buf.append(ch);
					}
					break;

				default:
					throw new IllegalStateException();
			}
		}
		if (buf.length() > 0 || state != DEFAULT) {
			result.add(buf.toString());
		}

		return result.toArray(new String[result.size()]);
	}

	private static String[] parseArgumentsImpl(String args, boolean split) {
		// man sh, see topic QUOTING
		List<String> result = new ArrayList<>();

		final int DEFAULT= 0;
		final int ARG= 1;
		final int IN_DOUBLE_QUOTE= 2;
		final int IN_SINGLE_QUOTE= 3;

		int state= DEFAULT;
		StringBuilder buf= new StringBuilder();
		int len= args.length();
		for (int i= 0; i < len; i++) {
			char ch= args.charAt(i);
			if (Character.isWhitespace(ch)) {
				if (state == DEFAULT) {
					// skip
					continue;
				} else if (state == ARG) {
					state= DEFAULT;
					result.add(buf.toString());
					buf.setLength(0);
					continue;
				}
			}
			switch (state) {
				case DEFAULT:
				case ARG:
					if (ch == '"') {
						if (split) {
							buf.append(ch);
						}
						state= IN_DOUBLE_QUOTE;
					} else if (ch == '\'') {
						if (split) {
							buf.append(ch);
						}
						state= IN_SINGLE_QUOTE;
					} else if (ch == '\\' && i + 1 < len) {
						if (split) {
							buf.append(ch);
						}
						state= ARG;
						ch= args.charAt(++i);
						buf.append(ch);
					} else {
						state= ARG;
						buf.append(ch);
					}
					break;

				case IN_DOUBLE_QUOTE:
					if (ch == '"') {
						if (split) {
							buf.append(ch);
						}
						state= ARG;
					} else if (ch == '\\' && i + 1 < len &&
							(args.charAt(i + 1) == '\\' || args.charAt(i + 1) == '"')) {
						if (split) {
							buf.append(ch);
						}
						ch= args.charAt(++i);
						buf.append(ch);
					} else {
						buf.append(ch);
					}
					break;

				case IN_SINGLE_QUOTE:
					if (ch == '\'') {
						if (split) {
							buf.append(ch);
						}
						state= ARG;
					} else {
						buf.append(ch);
					}
					break;

				default:
					throw new IllegalStateException();
			}
		}
		if (buf.length() > 0 || state != DEFAULT) {
			result.add(buf.toString());
		}

		return result.toArray(new String[result.size()]);
	}

	/**
	 * Parses the given command line into separate arguments that can be passed
	 * to <code>DebugPlugin.exec(String[], File)</code>. Embedded quotes and
	 * backslashes are interpreted, i.e. the resulting arguments are in the form
	 * that will be passed to an invoked process.
	 * <p>
	 * The reverse operation is {@link #renderArguments(String[], int[])}.
	 * </p>
	 *
	 * @param args command line arguments as a single string
	 * @return individual arguments
	 * @see #renderArguments(String[], int[])
	 * @since 3.1
	 */
	public static String[] parseArguments(String args) {
		if (args == null) {
			return new String[0];
		}

		if (Constants.OS_WIN32.equals(Platform.getOS())) {
			return parseArgumentsWindows(args, false);
		}

		return parseArgumentsImpl(args, false);
	}

	/**
	 * Splits the given command line into separate arguments that can be
	 * concatenated with a space as joiner. Embedded quotes and backslashes are
	 * kept as is (i.e. not interpreted).
	 * <p>
	 * Use this method to avoid e.g. losing quotes around an argument like
	 * <code>"${env_var:A}"</code>, which may later be substituted by a string
	 * that contains spaces.
	 * </p>
	 *
	 * @param args command line arguments as a single string
	 * @return individual arguments in original form
	 * @since 3.10
	 */
	public static String[] splitArguments(String args) {
		if (args == null) {
			return new String[0];
		}

		if (Constants.OS_WIN32.equals(Platform.getOS())) {
			return parseArgumentsWindows(args, true);
		}

		return parseArgumentsImpl(args, true);
	}

	/**
	 * Renders the given array of argument strings into a single command line.
	 * <p>
	 * If an argument contains whitespace, it it quoted. Contained quotes or
	 * backslashes will be escaped.
	 * </p>
	 * <p>
	 * If <code>segments</code> is not <code>null</code>, the array is filled
	 * with the offsets of the start positions of arguments 1 to
	 * <code>arguments.length - 1</code>, as rendered in the resulting string.
	 * </p>
	 *
	 * @param arguments the command line arguments
	 * @param segments an array of size <code>arguments.length - 1</code> or
	 *            <code>null</code>
	 * @return the command line
	 * @see #parseArguments(String)
	 * @since 3.8
	 */
	public static String renderArguments(String[] arguments, int[] segments) {
		boolean isWin32= Platform.getOS().equals(Constants.OS_WIN32);
		StringBuilder buf = new StringBuilder();
		int count = arguments.length;
		for (int i = 0; i < count; i++) {
			if (i > 0) {
				buf.append(' ');
			}

			boolean containsSpace = false;
			char[] characters = arguments[i].toCharArray();
			for (char ch : characters) {
				if (ch == ' ' || ch == '\t') {
					containsSpace = true;
					buf.append('"');
					break;
				}
			}

			int backslashes = 0;
			for (int j = 0; j < characters.length; j++) {
				char ch = characters[j];
				if (ch == '"') {
					if (isWin32) {
						if (j == 0 && characters.length == 2 && characters[1] == '"') {
							// empty string on windows platform, see bug 130767. Bug in constructor of JDK's java.lang.ProcessImpl.
							buf.append("\"\""); //$NON-NLS-1$
							break;
						}
						if (backslashes > 0) {
							// Feature in Windows: need to double-escape backslashes in front of double quote.
							for (; backslashes > 0; backslashes--) {
								buf.append('\\');
							}
						}
					}
					buf.append('\\');
				} else if (ch == '\\') {
					if (isWin32) {
						backslashes++;
					} else {
						buf.append('\\');
					}
				} else if (isWin32) {
					backslashes = 0;
				}
				buf.append(ch);
			}
			if (containsSpace) {
				buf.append('"');
			} else if (characters.length == 0) {
				buf.append("\"\""); //$NON-NLS-1$
			}

			if (segments != null && i < count - 1) {
				segments[i] = buf.length() + 1;
			}
		}
		return buf.toString();
	}

	/**
	 * Sets whether step filters should be applied to step commands. This
	 * setting is a global option applied to all registered debug targets.
	 *
	 * @param useStepFilters whether step filters should be applied to step
	 *  commands
	 * @since 3.3
	 * @see org.eclipse.debug.core.model.IStepFilters
	 */
	public static void setUseStepFilters(boolean useStepFilters) {
		getStepFilterManager().setUseStepFilters(useStepFilters);
	}

	/**
	 * Returns whether step filters are applied to step commands.
	 *
	 * @return whether step filters are applied to step commands
	 * @since 3.3
	 * @see org.eclipse.debug.core.model.IStepFilters
	 * @see org.eclipse.debug.core.commands.IStepFiltersHandler
	 */
	public static boolean isUseStepFilters() {
		return getStepFilterManager().isUseStepFilters();
	}

	/**
	 * Returns any step filters that have been contributed for the given model
	 * identifier.
	 *
	 * @param modelIdentifier the model identifier
	 * @return step filters that have been contributed for the given model
	 *         identifier, possibly an empty collection
	 * @since 3.10
	 * @see org.eclipse.debug.core.model.IStepFilter
	 */
	public static IStepFilter[] getStepFilters(String modelIdentifier) {
		return getStepFilterManager().getStepFilters(modelIdentifier);
	}

	/**
	 * Returns the step filter manager.
	 *
	 * @return step filter manager
	 */
	private static StepFilterManager getStepFilterManager() {
		return ((LaunchManager)getDefault().getLaunchManager()).getStepFilterManager();
	}

	/**
	 * Returns an adapter of the specified type for the given object or <code>null</code>
	 * if none. The object itself is returned if it is an instance of the specified type.
	 * If the object is adaptable and does not subclass <code>PlatformObject</code>, and
	 * does not provide the specified adapter directly, the platform's adapter manager
	 * is consulted for an adapter.
	 *
	 * @param element element to retrieve adapter for
	 * @param type adapter type
	 * @return adapter or <code>null</code>
	 * @since 3.4
	 */
	public static Object getAdapter(Object element, Class<?> type) {
		Object adapter = null;
		if (element != null) {
			if (type.isInstance(element)) {
				return element;
			} else {
				if (element instanceof IAdaptable) {
					adapter = ((IAdaptable)element).getAdapter(type);
				}
				// for objects that don't subclass PlatformObject, check the platform's adapter manager
				if (adapter == null && !(element instanceof PlatformObject)) {
					adapter = Platform.getAdapterManager().getAdapter(element, type);
				}
				// force load the adapter in case it really is available
				if (adapter == null) {
					adapter = Platform.getAdapterManager().loadAdapter(element, type.getName());
				}
			}
		}
		return adapter;
	}

}


