/*******************************************************************************
 * Copyright (c) 2000, 2010 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Nikolay Botev - bug 240651
 *******************************************************************************/
package org.eclipse.ui.internal;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.internal.provisional.action.ICoolBarManager2;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.ActiveShellExpression;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorLauncher;
import org.eclipse.ui.IEditorMatchingStrategy;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IEditorRegistry;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.IPersistableEditor;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.ISaveablePart;
import org.eclipse.ui.ISaveablePart2;
import org.eclipse.ui.ISaveablesLifecycleListener;
import org.eclipse.ui.ISaveablesSource;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPart3;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.Saveable;
import org.eclipse.ui.dialogs.ListSelectionDialog;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.StartupThreading.StartupRunnable;
import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor;
import org.eclipse.ui.internal.editorsupport.ComponentSupport;
import org.eclipse.ui.internal.misc.ExternalEditor;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.misc.UIStats;
import org.eclipse.ui.internal.part.NullEditorInput;
import org.eclipse.ui.internal.registry.EditorDescriptor;
import org.eclipse.ui.internal.registry.EditorRegistry;
import org.eclipse.ui.internal.tweaklets.TabBehaviour;
import org.eclipse.ui.internal.tweaklets.Tweaklets;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.model.WorkbenchPartLabelProvider;
import org.eclipse.ui.part.AbstractMultiEditor;
import org.eclipse.ui.part.MultiEditor;
import org.eclipse.ui.part.MultiEditorInput;
import org.eclipse.ui.statushandlers.StatusManager;

/**
 * Manage a group of element editors. Prevent the creation of two editors on the
 * same element.
 * 
 * 06/12/00 - DS - Given the ambiguous editor input type, the manager delegates
 * a number of responsibilities to the editor itself.
 * 
 * <ol>
 * <li>The editor should determine its own title.</li>
 * <li>The editor should listen to resource deltas and close itself if the
 * input is deleted. It may also choose to stay open if the editor has dirty
 * state.</li>
 * <li>The editor should persist its own state plus editor input.</li>
 * </ol>
 */
public class EditorManager implements IExtensionChangeHandler {
	EditorAreaHelper editorPresentation;

	WorkbenchWindow window;

	WorkbenchPage page;

	private Map actionCache = new HashMap();

	private static final String PIN_EDITOR_KEY = "PIN_EDITOR"; //$NON-NLS-1$

	private static final String PIN_EDITOR = "ovr16/pinned_ovr.gif"; //$NON-NLS-1$

	// When the user removes or adds the close editors automatically preference
	// the icon should be removed or added accordingly
	private IPropertyChangeListener editorPropChangeListnener = null;

	// Handler for the pin editor keyboard shortcut
	private IHandlerActivation pinEditorHandlerActivation = null;

	static final String RESOURCES_TO_SAVE_MESSAGE = WorkbenchMessages.EditorManager_saveResourcesMessage;

	static final String SAVE_RESOURCES_TITLE = WorkbenchMessages.EditorManager_saveResourcesTitle;

	/**
	 * EditorManager constructor comment.
	 */
	public EditorManager(WorkbenchWindow window, WorkbenchPage workbenchPage,
			EditorAreaHelper pres) {
		Assert.isNotNull(window);
		Assert.isNotNull(workbenchPage);
		Assert.isNotNull(pres);
		this.window = window;
		this.page = workbenchPage;
		this.editorPresentation = pres;

		page.getExtensionTracker().registerHandler(this, null);
	}

	/**
	 * Check to determine if the editor resources are no longer needed removes
	 * property change listener for editors removes pin editor keyboard shortcut
	 * handler disposes cached images and clears the cached images hash table
	 */
	void checkDeleteEditorResources() {
		// get the current number of editors
		IEditorReference[] editors = page.getEditorReferences();
		// If there are no editors
		if (editors.length == 0) {
			if (editorPropChangeListnener != null) {
				// remove property change listener for editors
				IPreferenceStore prefStore = WorkbenchPlugin.getDefault()
						.getPreferenceStore();
				prefStore
						.removePropertyChangeListener(editorPropChangeListnener);
				editorPropChangeListnener = null;
			}
			if (pinEditorHandlerActivation != null) {
				// remove pin editor keyboard shortcut handler
				final IHandlerService handlerService = (IHandlerService) window.getWorkbench().getService(IHandlerService.class);
				handlerService.deactivateHandler(pinEditorHandlerActivation);
				pinEditorHandlerActivation = null;
			}
		}
	}

	/**
	 * Check to determine if the property change listener for editors should be
	 * created
	 */
	void checkCreateEditorPropListener() {
		if (editorPropChangeListnener == null) {
			// Add a property change listener for closing editors automatically
			// preference
			// Add or remove the pin icon accordingly
			editorPropChangeListnener = new IPropertyChangeListener() {
				public void propertyChange(PropertyChangeEvent event) {
					if (event.getProperty().equals(
							IPreferenceConstants.REUSE_EDITORS_BOOLEAN)) {
						IEditorReference[] editors = getEditors();
						for (int i = 0; i < editors.length; i++) {
							((EditorReference) editors[i]).pinStatusUpdated();
						}
					}
				}
			};
			WorkbenchPlugin.getDefault().getPreferenceStore()
					.addPropertyChangeListener(editorPropChangeListnener);
		}
	}

	/**
	 * Check to determine if the handler for the pin editor keyboard shortcut
	 * should be created.
	 */
	void checkCreatePinEditorShortcutKeyHandler() {
		if (pinEditorHandlerActivation == null) {
			final Shell shell = window.getShell();
			final IHandler pinEditorHandler = new AbstractHandler() {
				public final Object execute(final ExecutionEvent event) {
					// check if the "Close editors automatically" preference is
					// set
					IPreferenceStore store = WorkbenchPlugin.getDefault().getPreferenceStore();
					if (store
							.getBoolean(IPreferenceConstants.REUSE_EDITORS_BOOLEAN)
							|| ((TabBehaviour)Tweaklets.get(TabBehaviour.KEY)).alwaysShowPinAction()) {

						IWorkbenchPartReference ref = editorPresentation
								.getVisibleEditor();
						if (ref instanceof WorkbenchPartReference) {
							WorkbenchPartReference concreteRef = (WorkbenchPartReference) ref;

							concreteRef.setPinned(concreteRef.isPinned());
						}
					}
					return null;
				}
			};

			// Assign the handler for the pin editor keyboard shortcut.
			final IHandlerService handlerService = (IHandlerService) window.getWorkbench().getService(IHandlerService.class);
			pinEditorHandlerActivation = handlerService.activateHandler(
					IWorkbenchCommandConstants.WINDOW_PIN_EDITOR, pinEditorHandler,
					new ActiveShellExpression(shell));
		}
	}

	/**
	 * Method to create the editor's pin ImageDescriptor
	 * 
	 * @return the single image descriptor for the editor's pin icon
	 */
	ImageDescriptor getEditorPinImageDesc() {
		ImageRegistry registry = JFaceResources.getImageRegistry();
		ImageDescriptor pinDesc = registry.getDescriptor(PIN_EDITOR_KEY);
		// Avoid registering twice
		if (pinDesc == null) {
			pinDesc = WorkbenchImages.getWorkbenchImageDescriptor(PIN_EDITOR);
			registry.put(PIN_EDITOR_KEY, pinDesc);

		}
		return pinDesc;
	}

	/**
	 * Answer a list of dirty editors.
	 */
	private List collectDirtyEditors() {
		List result = new ArrayList(3);
		IEditorReference[] editors = page.getEditorReferences();
		for (int i = 0; i < editors.length; i++) {
			IEditorPart part = (IEditorPart) editors[i].getPart(false);
			if (part != null && part.isDirty()) {
				result.add(part);
			}

		}
		return result;
	}

	/**
	 * Returns whether the manager contains an editor.
	 */
	public boolean containsEditor(IEditorReference ref) {
		IEditorReference[] editors = page.getEditorReferences();
		for (int i = 0; i < editors.length; i++) {
			if (ref == editors[i]) {
				return true;
			}
		}
		return false;
	}

	/*
	 * Creates the action bars for an editor. Editors of the same type should
	 * share a single editor action bar, so this implementation may return an
	 * existing action bar vector.
	 */
	private EditorActionBars createEditorActionBars(EditorDescriptor desc,
			final IEditorSite site) {
		// Get the editor type.
		String type = desc.getId();

		// If an action bar already exists for this editor type return it.
		EditorActionBars actionBars = (EditorActionBars) actionCache.get(type);
		if (actionBars != null) {
			actionBars.addRef();
			return actionBars;
		}

		// Create a new action bar set.
		actionBars = new EditorActionBars(page, site.getWorkbenchWindow(), type);
		actionBars.addRef();
		actionCache.put(type, actionBars);

		// Read base contributor.
		IEditorActionBarContributor contr = desc.createActionBarContributor();
		if (contr != null) {
			actionBars.setEditorContributor(contr);
			contr.init(actionBars, page);
		}

		// Read action extensions.
		EditorActionBuilder builder = new EditorActionBuilder();
		contr = builder.readActionExtensions(desc);
		if (contr != null) {
			actionBars.setExtensionContributor(contr);
			contr.init(actionBars, page);
		}

		// Return action bars.
		return actionBars;
	}

	/*
	 * Creates the action bars for an editor.
	 */
	private EditorActionBars createEmptyEditorActionBars(final IEditorSite site) {
		// Get the editor type.
		String type = String.valueOf(System.currentTimeMillis());

		// Create a new action bar set.
		// Note: It is an empty set.
		EditorActionBars actionBars = new EditorActionBars(page, site.getWorkbenchWindow(), type);
		actionBars.addRef();
		actionCache.put(type, actionBars);

		// Return action bars.
		return actionBars;
	}

	/*
	 * Dispose
	 */
	void disposeEditorActionBars(EditorActionBars actionBars) {
		actionBars.removeRef();
		if (actionBars.getRef() <= 0) {
			String type = actionBars.getEditorType();
			actionCache.remove(type);
			// refresh the cool bar manager before disposing of a cool item
			ICoolBarManager2 coolBar = (ICoolBarManager2) window.getCoolBarManager2();
            if (coolBar != null) {
            	coolBar.refresh();
			}
			actionBars.dispose();
		}
	}

	/**
	 * Returns an open editor matching the given editor input. If none match,
	 * returns <code>null</code>.
	 * 
	 * @param input
	 *            the editor input
	 * @return the matching editor, or <code>null</code> if no match found
	 */
	public IEditorPart findEditor(IEditorInput input) {
		return findEditor(null, input, IWorkbenchPage.MATCH_INPUT);
	}

	/**
	 * Returns an open editor matching the given editor input and/or editor id,
	 * as specified by matchFlags. If none match, returns <code>null</code>.
	 * 
	 * @param editorId
	 *            the editor id
	 * @param input
	 *            the editor input
	 * @param matchFlags
	 *            flags specifying which aspects to match
	 * @return the matching editor, or <code>null</code> if no match found
	 * @since 3.1
	 */
	public IEditorPart findEditor(String editorId, IEditorInput input,
			int matchFlags) {
		IEditorReference[] refs = findEditors(input, editorId, matchFlags);
		if (refs.length == 0) {
			return null;
		}
		return refs[0].getEditor(true);
	}

	/**
	 * Returns the open editor references matching the given editor input and/or
	 * editor id, as specified by matchFlags. If none match, returns an empty
	 * array.
	 * 
	 * @param editorId
	 *            the editor id
	 * @param input
	 *            the editor input
	 * @param matchFlags
	 *            flags specifying which aspects to match
	 * @return the matching editor, or an empty array if no match found
	 * @since 3.1
	 */
	public IEditorReference[] findEditors(IEditorInput input, String editorId,
			int matchFlags) {
		if (matchFlags == IWorkbenchPage.MATCH_NONE) {
			return new IEditorReference[0];
		}
		List result = new ArrayList();
		ArrayList othersList = new ArrayList(Arrays.asList(page
				.getEditorReferences()));
		if (!othersList.isEmpty()) {
			IEditorReference active = page.getActiveEditorReference();
			if (active != null) {
				othersList.remove(active);
				ArrayList activeList = new ArrayList(1);
				activeList.add(active);
				findEditors(activeList, input, editorId, matchFlags, result);
			}
			findEditors(othersList, input, editorId, matchFlags, result);
		}
		return (IEditorReference[]) result.toArray(new IEditorReference[result
				.size()]);
	}

	/**
	 * Populates a list with open editor(s) matching the given editor id and/or editor input.
	 * 
	 * @param editorId
	 *            the editor id
	 * @param input
	 *            the editor input
	 * @param editorList
	 *            a mutable list containing the references for the editors to
	 *            check (warning: items may be removed)
	 * @param result
	 *            the list to which matching editor references should be added
	 * @since 3.1
	 */
	private void findEditors(List editorList, IEditorInput input,
			String editorId, int matchFlags, List result) {
		if (matchFlags == IWorkbenchPage.MATCH_NONE) {
			return;
		}

		// Phase 0: Remove editors whose ids don't match (if matching by id)
		if (((matchFlags & IWorkbenchPage.MATCH_ID) != 0) && editorId != null) {
			for (Iterator i = editorList.iterator(); i.hasNext();) {
				EditorReference editor = (EditorReference) i.next();
				if (!editorId.equals(editor.getId())) {
					i.remove();
				}
			}
		}

		// If not matching on editor input, just return the remaining editors.
		// In practice, this case is never used.
		if ((matchFlags & IWorkbenchPage.MATCH_INPUT) == 0) {
			result.addAll(editorList);
			return;
		}

		// Phase 1: check editors that have their own matching strategy
		for (Iterator i = editorList.iterator(); i.hasNext();) {
			EditorReference editor = (EditorReference) i.next();
			IEditorDescriptor desc = editor.getDescriptor();
			if (desc != null) {
				IEditorMatchingStrategy matchingStrategy = desc
						.getEditorMatchingStrategy();
				if (matchingStrategy != null) {
					i.remove(); // We're handling this one here, so remove it
					// from the list.
					if (matchingStrategy.matches(editor, input)) {
						result.add(editor);
					}
				}
			}
		}

		// Phase 2: check materialized editors (without their own matching
		// strategy)
		for (Iterator i = editorList.iterator(); i.hasNext();) {
			IEditorReference editor = (IEditorReference) i.next();
			IEditorPart part = (IEditorPart) editor.getPart(false);
			if (part != null) {
				i.remove(); // We're handling this one here, so remove it from
				// the list.
				if (part.getEditorInput() != null
						&& part.getEditorInput().equals(input)) {
					result.add(editor);
				}
			}
		}

		// Phase 3: check unmaterialized editors for input equality,
		// delaying plug-in activation further by only restoring the editor
		// input
		// if the editor reference's factory id and name match.
		String name = input.getName();
		IPersistableElement persistable = input.getPersistable();
		if (name == null || persistable == null) {
			return;
		}
		String id = persistable.getFactoryId();
		if (id == null) {
			return;
		}
		for (Iterator i = editorList.iterator(); i.hasNext();) {
			EditorReference editor = (EditorReference) i.next();
			if (name.equals(editor.getName())
					&& id.equals(editor.getFactoryId())) {
				IEditorInput restoredInput;
				try {
					restoredInput = editor.getEditorInput();
					if (Util.equals(restoredInput, input)) {
						result.add(editor);
					}
				} catch (PartInitException e1) {
					WorkbenchPlugin.log(e1);
				}
			}
		}
	}

	/**
	 * Returns the SWT Display.
	 */
	private Display getDisplay() {
		return window.getShell().getDisplay();
	}

	/**
	 * Answer the number of editors.
	 */
	public int getEditorCount() {
		return page.getEditorReferences().length;
	}

	/*
	 * Answer the editor registry.
	 */
	private IEditorRegistry getEditorRegistry() {
		return WorkbenchPlugin.getDefault().getEditorRegistry();
	}

	/*
	 * See IWorkbenchPage.
	 */
	public IEditorPart[] getDirtyEditors() {
		List dirtyEditors = collectDirtyEditors();
		return (IEditorPart[]) dirtyEditors
				.toArray(new IEditorPart[dirtyEditors.size()]);
	}

	/*
	 * See IWorkbenchPage.
	 */
	public IEditorReference[] getEditors() {
		return page.getEditorReferences();
	}

	/*
	 * See IWorkbenchPage#getFocusEditor
	 */
	public IEditorPart getVisibleEditor() {
		IEditorReference ref = editorPresentation.getVisibleEditor();
		if (ref == null) {
			return null;
		}
		return (IEditorPart) ref.getPart(true);
	}

	/**
	 * Answer true if save is needed in any one of the editors.
	 */
	public boolean isSaveAllNeeded() {
		IEditorReference[] editors = page.getEditorReferences();
		for (int i = 0; i < editors.length; i++) {
			IEditorReference ed = editors[i];
			if (ed.isDirty()) {
				return true;
			}
		}
		return false;
	}

	/*
	 * Prompt the user to save the reusable editor. Return false if a new editor
	 * should be opened.
	 */
	private IEditorReference findReusableEditor(EditorDescriptor desc) {
		return ((TabBehaviour)Tweaklets.get(TabBehaviour.KEY)).findReusableEditor(page);
	}

	/**
	 * @param editorId
	 *            the editor part id
	 * @param input
	 *            the input
	 * @param setVisible
	 *            if this is to be created visible ... not used
	 * @param editorState
	 *            an {@link IMemento} &lt;editorState&gt; for persistable
	 *            editors. Can be <code>null</code>.
	 * @return a created editor reference
	 * @throws PartInitException
	 */
	public IEditorReference openEditor(String editorId, IEditorInput input,
			boolean setVisible, IMemento editorState) throws PartInitException {
		if (editorId == null || input == null) {
			throw new IllegalArgumentException();
		}

		IEditorRegistry reg = getEditorRegistry();
		EditorDescriptor desc = (EditorDescriptor) reg.findEditor(editorId);
		if (desc == null) {
			throw new PartInitException(NLS.bind(
					WorkbenchMessages.EditorManager_unknownEditorIDMessage,
					editorId));
		}

		IEditorReference result = openEditorFromDescriptor(desc, input, editorState);
		return result;
	}

	/*
	 * Open a new editor
	 */
	public IEditorReference openEditorFromDescriptor(EditorDescriptor desc,
			IEditorInput input, IMemento editorState) throws PartInitException {
		IEditorReference result = null;
		if (desc.isInternal()) {
			result = reuseInternalEditor(desc, input);
			if (result == null) {
				result = new EditorReference(this, input, desc, editorState);
			}
		} else if (desc.getId()
				.equals(IEditorRegistry.SYSTEM_INPLACE_EDITOR_ID)) {
			if (ComponentSupport.inPlaceEditorSupported()) {
				result = new EditorReference(this, input, desc);
			}
		} else if (desc.getId().equals(
				IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID)) {
			IPathEditorInput pathInput = getPathEditorInput(input);
			if (pathInput != null) {
				result = openSystemExternalEditor(pathInput.getPath());
			} else {
				throw new PartInitException(
						WorkbenchMessages.EditorManager_systemEditorError);
			}
		} else if (desc.isOpenExternal()) {
			result = openExternalEditor(desc, input);
		} else {
			// this should never happen
			throw new PartInitException(NLS.bind(
					WorkbenchMessages.EditorManager_invalidDescriptor, desc
							.getId()));
		}

		if (result != null) {
			createEditorTab((EditorReference) result, ""); //$NON-NLS-1$
		}

		Workbench wb = (Workbench) window.getWorkbench();
		wb.getEditorHistory().add(input, desc);
		return result;
	}

	/**
	 * Open a specific external editor on an file based on the descriptor.
	 */
	private IEditorReference openExternalEditor(final EditorDescriptor desc,
			IEditorInput input) throws PartInitException {
		final CoreException ex[] = new CoreException[1];

		final IPathEditorInput pathInput = getPathEditorInput(input);
		if (pathInput != null && pathInput.getPath() != null) {
			BusyIndicator.showWhile(getDisplay(), new Runnable() {
				public void run() {
					try {
						if (desc.getLauncher() != null) {
							// open using launcher
							Object launcher = WorkbenchPlugin.createExtension(
									desc.getConfigurationElement(), "launcher"); //$NON-NLS-1$
							((IEditorLauncher) launcher).open(pathInput
									.getPath());
						} else {
							// open using command
							ExternalEditor oEditor = new ExternalEditor(
									pathInput.getPath(), desc);
							oEditor.open();
						}
					} catch (CoreException e) {
						ex[0] = e;
					}
				}
			});
		} else {
			throw new PartInitException(NLS.bind(
					WorkbenchMessages.EditorManager_errorOpeningExternalEditor,
					desc.getFileName(), desc.getId()));
		}

		if (ex[0] != null) {
			throw new PartInitException(NLS.bind(
					WorkbenchMessages.EditorManager_errorOpeningExternalEditor,
					desc.getFileName(), desc.getId()), ex[0]);
		}

		// we do not have an editor part for external editors
		return null;
	}

	/**
	 * Create the part and reference for each inner editor.
	 * 
	 * @param ref
	 *            the AbstractMultiEditor reference
	 * @param part
	 *            the part
	 * @param input
	 *            the AbstractMultiEditor input
	 * @return the array of inner references to store in the AbstractMultiEditor reference
	 */
	IEditorReference[] openMultiEditor(final IEditorReference ref,
			final AbstractMultiEditor part, final MultiEditorInput input)
			throws PartInitException {

		String[] editorArray = input.getEditors();
		IEditorInput[] inputArray = input.getInput();

		// find all descriptors
		EditorDescriptor[] descArray = new EditorDescriptor[editorArray.length];
		IEditorReference refArray[] = new IEditorReference[editorArray.length];
		IEditorPart partArray[] = new IEditorPart[editorArray.length];

		IEditorRegistry reg = getEditorRegistry();
		for (int i = 0; i < editorArray.length; i++) {
			EditorDescriptor innerDesc = (EditorDescriptor) reg
					.findEditor(editorArray[i]);
			if (innerDesc == null) {
				throw new PartInitException(NLS.bind(
						WorkbenchMessages.EditorManager_unknownEditorIDMessage,
						editorArray[i]));
			}
			descArray[i] = innerDesc;
			InnerEditor innerRef = new InnerEditor(ref, part, inputArray[i],
					descArray[i]);
			refArray[i] = innerRef;
			partArray[i] = innerRef.getEditor(true);
		}
		part.setChildren(partArray);
		return refArray;
	}

	/*
	 * Opens an editor part.
	 */
	private void createEditorTab(final EditorReference ref,
			final String workbookId) throws PartInitException {

		editorPresentation.addEditor(ref, workbookId, true);

	}

	/*
	 * Create the site and initialize it with its action bars.
	 */
	EditorSite createSite(final IEditorReference ref, final IEditorPart part,
			final EditorDescriptor desc, final IEditorInput input)
			throws PartInitException {
		EditorSite site = new EditorSite(ref, part, page, desc);
		if (desc != null) {
			site.setActionBars(createEditorActionBars(desc, site));
		} else {
			site.setActionBars(createEmptyEditorActionBars(site));
		}
		final String label = part.getTitle(); // debugging only
		try {
			try {
				UIStats.start(UIStats.INIT_PART, label);
				part.init(site, input);
			} finally {
				UIStats.end(UIStats.INIT_PART, part, label);
			}

			// Sanity-check the site
			if (part.getSite() != site || part.getEditorSite() != site) {
				throw new PartInitException(NLS.bind(
						WorkbenchMessages.EditorManager_siteIncorrect, desc
								.getId()));
			}

		} catch (Exception e) {
			disposeEditorActionBars((EditorActionBars) site.getActionBars());
			site.dispose();
			if (e instanceof PartInitException) {
				throw (PartInitException) e;
			}

			throw new PartInitException(
					WorkbenchMessages.EditorManager_errorInInit, e);
		}

		return site;
	}

	/*
	 * See IWorkbenchPage.
	 */
	private IEditorReference reuseInternalEditor(EditorDescriptor desc,
			IEditorInput input) throws PartInitException {

		Assert.isNotNull(desc, "descriptor must not be null"); //$NON-NLS-1$
		Assert.isNotNull(input, "input must not be null"); //$NON-NLS-1$

		IEditorReference reusableEditorRef = findReusableEditor(desc);
		if (reusableEditorRef != null) {
			return ((TabBehaviour) Tweaklets.get(TabBehaviour.KEY))
					.reuseInternalEditor(page, this, editorPresentation, desc,
							input, reusableEditorRef);
		}
		return null;
	}

	IEditorPart createPart(final EditorDescriptor desc)
			throws PartInitException {
		try {
			IEditorPart result = desc.createEditor();
			IConfigurationElement element = desc.getConfigurationElement();
			if (element != null) {
				page.getExtensionTracker().registerObject(
						element.getDeclaringExtension(), result,
						IExtensionTracker.REF_WEAK);
			}
			return result;
		} catch (CoreException e) {
			throw new PartInitException(StatusUtil.newStatus(
					desc.getPluginID(),
					WorkbenchMessages.EditorManager_instantiationError, e));
		}
	}

	/**
	 * Open a system external editor on the input path.
	 */
	private IEditorReference openSystemExternalEditor(final IPath location)
			throws PartInitException {
		if (location == null) {
			throw new IllegalArgumentException();
		}

		final boolean result[] = { false };
		BusyIndicator.showWhile(getDisplay(), new Runnable() {
			public void run() {
				if (location != null) {
					result[0] = Program.launch(location.toOSString());
				}
			}
		});

		if (!result[0]) {
			throw new PartInitException(NLS.bind(
					WorkbenchMessages.EditorManager_unableToOpenExternalEditor,
					location));
		}

		// We do not have an editor part for external editors
		return null;
	}

	ImageDescriptor findImage(EditorDescriptor desc, IPath path) {
		if (desc == null) {
			// @issue what should be the default image?
			return ImageDescriptor.getMissingImageDescriptor();
		}

		if (desc.isOpenExternal() && path != null) {
			return PlatformUI.getWorkbench().getEditorRegistry()
					.getImageDescriptor(path.toOSString());
		}

		return desc.getImageDescriptor();
	}

	/**
	 * @see org.eclipse.ui.IPersistable
	 */
	public IStatus restoreState(IMemento memento) {
		// Restore the editor area workbooks layout/relationship
		final MultiStatus result = new MultiStatus(PlatformUI.PLUGIN_ID,
				IStatus.OK,
				WorkbenchMessages.EditorManager_problemsRestoringEditors, null);
		final String activeWorkbookID[] = new String[1];
		final ArrayList visibleEditors = new ArrayList(5);
		final IEditorReference activeEditor[] = new IEditorReference[1];

		IMemento areaMem = memento.getChild(IWorkbenchConstants.TAG_AREA);
		if (areaMem != null) {
			result.add(editorPresentation.restoreState(areaMem));
			activeWorkbookID[0] = areaMem
					.getString(IWorkbenchConstants.TAG_ACTIVE_WORKBOOK);
		}

		// Loop through the editors.

		IMemento[] editorMems = memento
				.getChildren(IWorkbenchConstants.TAG_EDITOR);
		for (int x = 0; x < editorMems.length; x++) {
			// for dynamic UI - call restoreEditorState to replace code which is
			// commented out
			restoreEditorState(editorMems[x], visibleEditors, activeEditor,
					result);
		}

		// restore the presentation
		if (areaMem != null) {
			result.add(editorPresentation.restorePresentationState(areaMem));
		}
		try {
			StartupThreading.runWithThrowable(new StartupRunnable(){

				public void runWithException() throws Throwable {
					// Update each workbook with its visible editor.
					for (int i = 0; i < visibleEditors.size(); i++) {
						setVisibleEditor((IEditorReference) visibleEditors.get(i),
								false);
					}

					// Update the active workbook
					if (activeWorkbookID[0] != null) {
						editorPresentation
								.setActiveEditorWorkbookFromID(activeWorkbookID[0]);
					}

					if (activeEditor[0] != null) {
						IWorkbenchPart editor = activeEditor[0].getPart(true);

						if (editor != null) {
							page.activate(editor);
						}
					}
				}});
		}
		catch (Throwable t) {
			// The exception is already logged.
			result
					.add(new Status(
							IStatus.ERROR,
							PlatformUI.PLUGIN_ID,
							0,
							WorkbenchMessages.EditorManager_exceptionRestoringEditor,
							t));
		}
		
		return result;
	}

	/**
	 * Save all of the editors in the workbench. Return true if successful.
	 * Return false if the user has canceled the command.
	 * @param confirm true if the user should be prompted before the save
	 * @param closing true if the page is being closed
	 * @param addNonPartSources true if saveables from non-part sources should be saved too.
	 * @return false if the user canceled or an error occurred while saving
	 */
	public boolean saveAll(boolean confirm, boolean closing, boolean addNonPartSources) {
		// Get the list of dirty editors and views. If it is
		// empty just return.
		ISaveablePart[] parts = page.getDirtyParts();
		if (parts.length == 0) {
			return true;
		}
		// saveAll below expects a mutable list
		List dirtyParts = new ArrayList(parts.length);
		for (int i = 0; i < parts.length; i++) {
			dirtyParts.add(parts[i]);
		}

		// If confirmation is required ..
		return saveAll(dirtyParts, confirm, closing, addNonPartSources, window);
	}

	/**
	 * Saves the given dirty editors and views, optionally prompting the user.
	 * 
	 * @param dirtyParts
	 *            the dirty views and editors
	 * @param confirm
	 *            <code>true</code> to prompt whether to save, <code>false</code>
	 *            to save without prompting
	 * @param closing
	 *            <code>true</code> if the parts are being closed,
	 *            <code>false</code> if just being saved without closing
	 * @param addNonPartSources true if non-part sources should be saved too
	 * @param window
	 *            the window to use as the parent for the dialog that prompts to
	 *            save multiple dirty editors and views
	 * @return <code>true</code> on success, <code>false</code> if the user
	 *         canceled the save or an error occurred while saving
	 */
	public static boolean saveAll(List dirtyParts, boolean confirm, boolean closing,
			boolean addNonPartSources, final IWorkbenchWindow window) {
		return saveAll(dirtyParts, confirm, closing, addNonPartSources, window, window);
	}
	
	/**
	 * Saves the given dirty editors and views, optionally prompting the user.
	 * 
	 * @param dirtyParts
	 *            the dirty views and editors
	 * @param confirm
	 *            <code>true</code> to prompt whether to save,
	 *            <code>false</code> to save without prompting
	 * @param closing
	 *            <code>true</code> if the parts are being closed,
	 *            <code>false</code> if just being saved without closing
	 * @param addNonPartSources
	 *            true if non-part sources should be saved too
	 * @param runnableContext
	 *            the context in which to run long-running operations
	 * @param shellProvider
	 *            providing the shell to use as the parent for the dialog that
	 *            prompts to save multiple dirty editors and views
	 * @return <code>true</code> on success, <code>false</code> if the user
	 *         canceled the save
	 */
	public static boolean saveAll(List dirtyParts, final boolean confirm, final boolean closing,
				boolean addNonPartSources, final IRunnableContext runnableContext, final IShellProvider shellProvider) {
		// clone the input list
		dirtyParts = new ArrayList(dirtyParts);

		if (closing) {
			// if the parts are going to be closed, then we only save those that
			// need to be saved when closed, see bug 272070
			for (Iterator it = dirtyParts.iterator(); it.hasNext();) {
				ISaveablePart saveablePart = (ISaveablePart) it.next();
				if (!saveablePart.isSaveOnCloseNeeded()) {
					it.remove();
				}
			}
		}

    	List modelsToSave;
		if (confirm) {
			boolean saveable2Processed = false;
			// Process all parts that implement ISaveablePart2.
			// These parts are removed from the list after saving
			// them. We then need to restore the workbench to
			// its previous state, for now this is just last
			// active perspective.
			// Note that the given parts may come from multiple
			// windows, pages and perspectives.
			ListIterator listIterator = dirtyParts.listIterator();

			WorkbenchPage currentPage = null;
			Perspective currentPageOriginalPerspective = null;
			while (listIterator.hasNext()) {
				IWorkbenchPart part = (IWorkbenchPart) listIterator.next();
				if (part instanceof ISaveablePart2) {
					WorkbenchPage page = (WorkbenchPage) part.getSite()
							.getPage();
					if (!Util.equals(currentPage, page)) {
						if (currentPage != null
								&& currentPageOriginalPerspective != null) {
							if (!currentPageOriginalPerspective
									.equals(currentPage.getActivePerspective())) {
								currentPage
										.setPerspective(currentPageOriginalPerspective
												.getDesc());
							}
						}
						currentPage = page;
						currentPageOriginalPerspective = page
								.getActivePerspective();
					}
					if (confirm) {
						if (part instanceof IViewPart) {
							Perspective perspective = page
									.getFirstPerspectiveWithView((IViewPart) part);
							if (perspective != null) {
								page.setPerspective(perspective.getDesc());
							}
						}
						// show the window containing the page?
						IWorkbenchWindow partsWindow = page
								.getWorkbenchWindow();
						if (partsWindow != partsWindow.getWorkbench()
								.getActiveWorkbenchWindow()) {
							Shell shell = partsWindow.getShell();
							if (shell.getMinimized()) {
								shell.setMinimized(false);
							}
							shell.setActive();
						}
						page.bringToTop(part);
					}
					// try to save the part
					int choice = SaveableHelper.savePart((ISaveablePart2) part,
							page.getWorkbenchWindow(), confirm);
					if (choice == ISaveablePart2.CANCEL) {
						// If the user cancels, don't restore the previous
						// workbench state, as that will
						// be an unexpected switch from the current state.
						return false;
					} else if (choice != ISaveablePart2.DEFAULT) {
						saveable2Processed = true;
						listIterator.remove();
					}
				}
			}

			// try to restore the workbench to its previous state
			if (currentPage != null && currentPageOriginalPerspective != null) {
				if (!currentPageOriginalPerspective.equals(currentPage
						.getActivePerspective())) {
					currentPage.setPerspective(currentPageOriginalPerspective
							.getDesc());
				}
			}

			// if processing a ISaveablePart2 caused other parts to be
			// saved, remove them from the list presented to the user.
			if (saveable2Processed) {
				listIterator = dirtyParts.listIterator();
				while (listIterator.hasNext()) {
					ISaveablePart part = (ISaveablePart) listIterator.next();
					if (!part.isDirty()) {
						listIterator.remove();
					}
				}
			}

            modelsToSave = convertToSaveables(dirtyParts, closing, addNonPartSources);
            
            // If nothing to save, return.
            if (modelsToSave.isEmpty()) {
				return true;
			}
			boolean canceled = SaveableHelper.waitForBackgroundSaveJobs(modelsToSave);
			if (canceled) {
				return false;
			}
            // Use a simpler dialog if there's only one
            if (modelsToSave.size() == 1) {
            	Saveable model = (Saveable) modelsToSave.get(0);
				String message = NLS.bind(WorkbenchMessages.EditorManager_saveChangesQuestion, model.getName());
				// Show a dialog.
				String[] buttons = new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL, IDialogConstants.CANCEL_LABEL };
				MessageDialog d = new MessageDialog(shellProvider.getShell(),
						WorkbenchMessages.Save_Resource, null, message,
						MessageDialog.QUESTION, buttons, 0) {
					protected int getShellStyle() {
						return super.getShellStyle() | SWT.SHEET;
					}
				};
				
				int choice = SaveableHelper.testGetAutomatedResponse();
				if (SaveableHelper.testGetAutomatedResponse() == SaveableHelper.USER_RESPONSE) {
					choice = d.open();
				}

				// Branch on the user choice.
				// The choice id is based on the order of button labels
				// above.
				switch (choice) {
				case ISaveablePart2.YES: // yes
					break;
				case ISaveablePart2.NO: // no
					return true;
				default:
				case ISaveablePart2.CANCEL: // cancel
					return false;
				}
            }
            else {
	            ListSelectionDialog dlg = new ListSelectionDialog(
	                    shellProvider.getShell(), modelsToSave,
	                    new ArrayContentProvider(),
	                    new WorkbenchPartLabelProvider(), RESOURCES_TO_SAVE_MESSAGE) {
	            	protected int getShellStyle() {
	            		return super.getShellStyle() | SWT.SHEET;
	            	}
	            };
	            dlg.setInitialSelections(modelsToSave.toArray());
	            dlg.setTitle(SAVE_RESOURCES_TITLE);
	
	            // this "if" statement aids in testing.
	            if (SaveableHelper.testGetAutomatedResponse()==SaveableHelper.USER_RESPONSE) {
	            	int result = dlg.open();
	                //Just return false to prevent the operation continuing
	                if (result == IDialogConstants.CANCEL_ID) {
						return false;
					}
	
	                modelsToSave = Arrays.asList(dlg.getResult());
	            }
            }
        }
        else {
        	modelsToSave = convertToSaveables(dirtyParts, closing, addNonPartSources);
		}

        // If the editor list is empty return.
        if (modelsToSave.isEmpty()) {
			return true;
		}
		
		// Create save block.
        final List finalModels = modelsToSave;
		IRunnableWithProgress progressOp = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor) {
				IProgressMonitor monitorWrap = new EventLoopProgressMonitor(
						monitor);
				monitorWrap.beginTask(WorkbenchMessages.Saving_Modifications, finalModels.size());
				for (Iterator i = finalModels.iterator(); i.hasNext();) {
					Saveable model = (Saveable) i.next();
					// handle case where this model got saved as a result of saving another
					if (!model.isDirty()) {
						monitor.worked(1);
						continue;
					}
					SaveableHelper.doSaveModel(model, new SubProgressMonitor(monitorWrap, 1), shellProvider, closing || confirm);
					if (monitorWrap.isCanceled()) {
						break;
					}
				}
				monitorWrap.done();
			}
		};

		// Do the save.
		return SaveableHelper.runProgressMonitorOperation(
				WorkbenchMessages.Save_All, progressOp, runnableContext, shellProvider);
	}

	/**
	 * For each part (view or editor) in the given list, attempts to convert it
	 * to one or more saveable models. Duplicate models are removed. If closing
	 * is true, then models that will remain open in parts other than the given
	 * parts are removed.
	 * 
	 * @param parts
	 *            the parts (list of IViewPart or IEditorPart)
	 * @param closing
	 *            whether the parts are being closed
	 * @param addNonPartSources
	 *            whether non-part sources should be added (true for the Save
	 *            All action, see bug 139004)
	 * @return the dirty models
	 */
	private static List convertToSaveables(List parts, boolean closing, boolean addNonPartSources) {
		ArrayList result = new ArrayList();
		HashSet seen = new HashSet();
		for (Iterator i = parts.iterator(); i.hasNext();) {
			IWorkbenchPart part = (IWorkbenchPart) i.next();
			Saveable[] saveables = getSaveables(part);
			for (int j = 0; j < saveables.length; j++) {
				Saveable saveable = saveables[j];
				if (saveable.isDirty() && !seen.contains(saveable)) {
					seen.add(saveable);
					if (!closing
							|| closingLastPartShowingModel(saveable, parts, part
									.getSite().getPage())) {
						result.add(saveable);
					}
				}
			}
		}
		if (addNonPartSources) {
			SaveablesList saveablesList = (SaveablesList) PlatformUI
					.getWorkbench().getService(
							ISaveablesLifecycleListener.class);
			ISaveablesSource[] nonPartSources = saveablesList
					.getNonPartSources();
			for (int i = 0; i < nonPartSources.length; i++) {
				Saveable[] saveables = nonPartSources[i].getSaveables();
				for (int j = 0; j < saveables.length; j++) {
					Saveable saveable = saveables[j];
					if (saveable.isDirty() && !seen.contains(saveable)) {
						seen.add(saveable);
						result.add(saveable);
					}
				}
			}
		}
		return result;
	}

	/**
	 * Returns the saveable models provided by the given part.
	 * If the part does not provide any models, a default model
	 * is returned representing the part.
	 * 
	 * @param part the workbench part
	 * @return the saveable models
	 */
	private static Saveable[] getSaveables(IWorkbenchPart part) {
		if (part instanceof ISaveablesSource) {
			ISaveablesSource source = (ISaveablesSource) part;
			return source.getSaveables();
		}
		return new Saveable[] { new DefaultSaveable(part) };
	}

	/**
	 * Returns true if, in the given page, no more parts will reference the
	 * given model if the given parts are closed.
	 * 
	 * @param model
	 *            the model
	 * @param closingParts
	 *            the parts being closed (list of IViewPart or IEditorPart)
	 * @param page
	 *            the page
	 * @return <code>true</code> if no more parts in the page will reference
	 *         the given model, <code>false</code> otherwise
	 */
	private static boolean closingLastPartShowingModel(Saveable model,
			List closingParts, IWorkbenchPage page) {
		HashSet closingPartsWithSameModel = new HashSet();
		for (Iterator i = closingParts.iterator(); i.hasNext();) {
			IWorkbenchPart part = (IWorkbenchPart) i.next();
			Saveable[] models = getSaveables(part);
			if (Arrays.asList(models).contains(model)) {
				closingPartsWithSameModel.add(part);
			}
		}
		IWorkbenchPartReference[] pagePartRefs = ((WorkbenchPage) page).getAllParts();
		HashSet pagePartsWithSameModels = new HashSet();
		for (int i = 0; i < pagePartRefs.length; i++) {
			IWorkbenchPartReference partRef = pagePartRefs[i];
			IWorkbenchPart part = partRef.getPart(false);
			if (part != null) {
				Saveable[] models = getSaveables(part);
				if (Arrays.asList(models).contains(model)) {
					pagePartsWithSameModels.add(part);
				}
			}
		}
		for (Iterator i = closingPartsWithSameModel.iterator(); i.hasNext();) {
			IWorkbenchPart part = (IWorkbenchPart) i.next();
			pagePartsWithSameModels.remove(part);
		}
		return pagePartsWithSameModels.isEmpty();
	}
	
	/*
	 * Saves the workbench part.
	 */
	public boolean savePart(final ISaveablePart saveable, IWorkbenchPart part,
			boolean confirm) {
		return SaveableHelper.savePart(saveable, part, window, confirm);
	}

	/**
	 * @see IPersistablePart
	 */
	public IStatus saveState(final IMemento memento) {

		final MultiStatus result = new MultiStatus(PlatformUI.PLUGIN_ID,
				IStatus.OK,
				WorkbenchMessages.EditorManager_problemsSavingEditors, null);

		// Save the editor area workbooks layout/relationship
		IMemento editorAreaMem = memento
				.createChild(IWorkbenchConstants.TAG_AREA);
		result.add(editorPresentation.saveState(editorAreaMem));

		// Save the active workbook id
		editorAreaMem.putString(IWorkbenchConstants.TAG_ACTIVE_WORKBOOK,
				editorPresentation.getActiveEditorWorkbookID());

		// Get each workbook
		ArrayList workbooks = editorPresentation.getWorkbooks();

		for (Iterator iter = workbooks.iterator(); iter.hasNext();) {
			EditorStack workbook = (EditorStack) iter.next();

			// Use the list of editors found in EditorStack; fix for 24091
			EditorPane editorPanes[] = workbook.getEditors();

			for (int i = 0; i < editorPanes.length; i++) {
				// Save each open editor.
				IEditorReference editorReference = editorPanes[i]
						.getEditorReference();
				EditorReference e = (EditorReference) editorReference;
				final IEditorPart editor = editorReference.getEditor(false);
				if (editor == null) {
					if (e.getMemento() != null) {
						IMemento editorMem = memento
								.createChild(IWorkbenchConstants.TAG_EDITOR);
						editorMem.putMemento(e.getMemento());
					}
					continue;
				}

				// for dynamic UI - add the next line to replace the subsequent
				// code which is commented out
				saveEditorState(memento, e, result);
			}
		}
		return result;
	}

	/**
	 * Shows an editor. If <code>setFocus == true</code> then give it focus,
	 * too.
	 * 
	 * @return true if the active editor was changed, false if not.
	 */
	public boolean setVisibleEditor(IEditorReference newEd, boolean setFocus) {
		return editorPresentation.setVisibleEditor(newEd, setFocus);
	}

	private IPathEditorInput getPathEditorInput(IEditorInput input) {
		if (input instanceof IPathEditorInput) {
			return (IPathEditorInput) input;
		}

		return (IPathEditorInput) Util.getAdapter(input, IPathEditorInput.class);
	}

	private class InnerEditor extends EditorReference {

		private IEditorReference outerEditor;

		private AbstractMultiEditor outerEditorPart;

		public InnerEditor(IEditorReference outerEditor,
				AbstractMultiEditor outerEditorPart, IEditorInput input,
				EditorDescriptor desc) {
			super(EditorManager.this, input, desc);
			this.outerEditor = outerEditor;
			this.outerEditorPart = outerEditorPart;
		}

		protected void doDisposePart() {
			this.outerEditorPart = null;
			super.doDisposePart();
		}

		protected PartPane createPane() {
			// MultiEditor backwards compatibility
			return new MultiEditorInnerPane(
					(EditorPane) ((EditorReference) outerEditor).getPane(),
					this, page, editorPresentation.getActiveWorkbook(),
					outerEditorPart instanceof MultiEditor);
		}

		protected Composite getPaneControlContainer() {
			// MultiEditor backwards compatibility
			if (outerEditorPart instanceof MultiEditor) {
				return super.getPaneControlContainer();
			}
			return outerEditorPart.getInnerEditorContainer(this);
		}

	}

	/*
	 * Made public for Mylar in 3.3 - see bug 138666. Can be made private once
	 * we have real API for this.
	 */
	public void restoreEditorState(IMemento editorMem,
			ArrayList visibleEditors, IEditorReference[] activeEditor,
			MultiStatus result) {
		// String strFocus = editorMem.getString(IWorkbenchConstants.TAG_FOCUS);
		// boolean visibleEditor = "true".equals(strFocus); //$NON-NLS-1$
		final EditorReference e = new EditorReference(this, editorMem);

		// if the editor is not visible, ensure it is put in the correct
		// workbook. PR 24091

		final String workbookID = editorMem
				.getString(IWorkbenchConstants.TAG_WORKBOOK);

		try {
			StartupThreading.runWithPartInitExceptions(new StartupRunnable () {

				public void runWithException() throws Throwable {
					createEditorTab(e, workbookID);
				}});
			
		} catch (PartInitException ex) {
			result.add(ex.getStatus());
		}

		String strActivePart = editorMem
				.getString(IWorkbenchConstants.TAG_ACTIVE_PART);
		if ("true".equals(strActivePart)) { //$NON-NLS-1$
			activeEditor[0] = e;
		}

		String strFocus = editorMem.getString(IWorkbenchConstants.TAG_FOCUS);
		boolean visibleEditor = "true".equals(strFocus); //$NON-NLS-1$
		if (visibleEditor) {
			visibleEditors.add(e);
		}
	}

	// for dynamic UI
	protected void saveEditorState(IMemento mem, IEditorReference ed,
			MultiStatus res) {
		final EditorReference editorRef = (EditorReference) ed;
		final IEditorPart editor = ed.getEditor(false);
		final IMemento memento = mem;
		final MultiStatus result = res;
		if (!(editor.getEditorSite() instanceof EditorSite)) {
			return;
		}
		final EditorSite site = (EditorSite) editor.getEditorSite();
		if (site.getPane() instanceof MultiEditorInnerPane) {
			return;
		}

		SafeRunner.run(new SafeRunnable() {
			public void run() {
				// Get the input.
				IEditorInput input = editor.getEditorInput();
				if (!input.exists()) {
					return;
				}
				IPersistableElement persistable = input.getPersistable();
				if (persistable == null) {
					return;
				}

				// Save editor.
				IMemento editorMem = memento
						.createChild(IWorkbenchConstants.TAG_EDITOR);
				editorMem.putString(IWorkbenchConstants.TAG_TITLE, editorRef
						.getTitle());
				editorMem.putString(IWorkbenchConstants.TAG_NAME, editorRef
						.getName());
				editorMem.putString(IWorkbenchConstants.TAG_ID, editorRef
						.getId());
				editorMem.putString(IWorkbenchConstants.TAG_TOOLTIP, editorRef
						.getTitleToolTip());

				editorMem.putString(IWorkbenchConstants.TAG_PART_NAME,
						editorRef.getPartName());
				
				if (editor instanceof IWorkbenchPart3) {
					Map properties = ((IWorkbenchPart3) editor)
							.getPartProperties();
					if (!properties.isEmpty()) {
						IMemento propBag = editorMem
								.createChild(IWorkbenchConstants.TAG_PROPERTIES);
						Iterator i = properties.entrySet().iterator();
						while (i.hasNext()) {
							Map.Entry entry = (Map.Entry) i.next();
							IMemento p = propBag.createChild(
									IWorkbenchConstants.TAG_PROPERTY,
									(String) entry.getKey());
							p.putTextData((String) entry.getValue());
						}
					}
				}

				if (editorRef.isPinned()) {
					editorMem.putString(IWorkbenchConstants.TAG_PINNED, "true"); //$NON-NLS-1$
				}

				EditorPane editorPane = (EditorPane) ((EditorSite) editor
						.getEditorSite()).getPane();
				editorMem.putString(IWorkbenchConstants.TAG_WORKBOOK,
						editorPane.getWorkbook().getID());

				if (editor == page.getActiveEditor()) {
					editorMem.putString(IWorkbenchConstants.TAG_ACTIVE_PART,
							"true"); //$NON-NLS-1$
				}

				if (editorPane == editorPane.getWorkbook().getSelection()) {
					editorMem.putString(IWorkbenchConstants.TAG_FOCUS, "true"); //$NON-NLS-1$
				}

				if (input instanceof IPathEditorInput) {
					IPath path = ((IPathEditorInput) input).getPath();
					if (path != null) {
						editorMem.putString(IWorkbenchConstants.TAG_PATH, path
								.toString());
					}
				}

				// Save input.
				IMemento inputMem = editorMem
						.createChild(IWorkbenchConstants.TAG_INPUT);
				inputMem.putString(IWorkbenchConstants.TAG_FACTORY_ID,
						persistable.getFactoryId());
				persistable.saveState(inputMem);
				
				// any editors that want to persist state
				if (editor instanceof IPersistableEditor) {
					IMemento editorState = editorMem
							.createChild(IWorkbenchConstants.TAG_EDITOR_STATE);
					((IPersistableEditor) editor).saveState(editorState);
				}
			}

			public void handleException(Throwable e) {
				result
						.add(new Status(
								IStatus.ERROR,
								PlatformUI.PLUGIN_ID,
								0,
								NLS
										.bind(
												WorkbenchMessages.EditorManager_unableToSaveEditor,
												editorRef.getTitle()), e));
			}
		});
	}

	// for dynamic UI
	public IMemento getMemento(IEditorReference e) {
		if (e instanceof EditorReference) {
			return ((EditorReference) e).getMemento();
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#removeExtension(org.eclipse.core.runtime.IExtension,
	 *      java.lang.Object[])
	 */
	public void removeExtension(IExtension source, Object[] objects) {
		for (int i = 0; i < objects.length; i++) {
			if (objects[i] instanceof IEditorPart) {
				// close the editor and clean up the editor history

				IEditorPart editor = (IEditorPart) objects[i];
				IEditorInput input = editor.getEditorInput();
				page.closeEditor(editor, true);
				((Workbench) window.getWorkbench()).getEditorHistory().remove(
						input);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#addExtension(org.eclipse.core.runtime.dynamicHelpers.IExtensionTracker,
	 *      org.eclipse.core.runtime.IExtension)
	 */
	public void addExtension(IExtensionTracker tracker, IExtension extension) {
		// Nothing to do
	}

	/**
	 * @return
	 */
	/*package*/ IEditorReference openEmptyTab() {
		IEditorInput input = new NullEditorInput();
		EditorDescriptor desc = (EditorDescriptor) ((EditorRegistry) getEditorRegistry())
				.findEditor(EditorRegistry.EMPTY_EDITOR_ID);
		EditorReference result = new EditorReference(this, input, desc);
		try {
			createEditorTab(result, ""); //$NON-NLS-1$
			return result;
		} catch (PartInitException e) {
			StatusManager.getManager().handle(
					StatusUtil.newStatus(WorkbenchPlugin.PI_WORKBENCH, e));
		}
		return null;
	}
	
	public static boolean useIPersistableEditor() {
		IPreferenceStore store = WorkbenchPlugin.getDefault()
				.getPreferenceStore();
		return store.getBoolean(IPreferenceConstants.USE_IPERSISTABLE_EDITORS);
	}
}
