/*******************************************************************************
 * Copyright (c) 2006, 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
 *******************************************************************************/

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.Map;
import java.util.Set;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.AssertionFailedException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.ISaveablePart;
import org.eclipse.ui.ISaveablePart2;
import org.eclipse.ui.ISaveablesLifecycleListener;
import org.eclipse.ui.ISaveablesSource;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPreferenceConstants;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.Saveable;
import org.eclipse.ui.SaveablesLifecycleEvent;
import org.eclipse.ui.dialogs.ListSelectionDialog;
import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.util.PrefUtil;
import org.eclipse.ui.model.WorkbenchPartLabelProvider;

/**
 * The model manager maintains a list of open saveable models.
 * 
 * @see Saveable
 * @see ISaveablesSource
 * 
 * @since 3.2
 */
public class SaveablesList implements ISaveablesLifecycleListener {

	private ListenerList listeners = new ListenerList();

	// event source (mostly ISaveablesSource) -> Set of Saveable
	private Map modelMap = new HashMap();

	// reference counting map, Saveable -> Integer
	private Map modelRefCounts = new HashMap();

	private Set nonPartSources = new HashSet();

	/**
	 * Returns the list of open models managed by this model manager.
	 * 
	 * @return a list of models
	 */
	public Saveable[] getOpenModels() {
		Set allDistinctModels = new HashSet();
		Iterator saveables = modelMap.values().iterator();
		while (saveables.hasNext())
			allDistinctModels.addAll((Set)saveables.next());
		
		return (Saveable[]) allDistinctModels.toArray(
				new Saveable[allDistinctModels.size()]);
	}

	// returns true if this model has not yet been in getModels()
	private boolean addModel(Object source, Saveable model) {
		if (model == null) {
			logWarning(
					"Ignored attempt to add invalid saveable", source, model); //$NON-NLS-1$
			return false;
		}
		boolean result = false;
		Set modelsForSource = (Set) modelMap.get(source);
		if (modelsForSource == null) {
			modelsForSource = new HashSet();
			modelMap.put(source, modelsForSource);
		}
		if (modelsForSource.add(model)) {
			result = incrementRefCount(modelRefCounts, model);
		} else {
			logWarning(
					"Ignored attempt to add saveable that was already registered", source, model); //$NON-NLS-1$
		}
		return result;
	}

	/**
	 * returns true if the given key was added for the first time
	 * 
	 * @param referenceMap
	 * @param key
	 * @return true if the ref count of the given key is now 1
	 */
	private boolean incrementRefCount(Map referenceMap, Object key) {
		boolean result = false;
		Integer refCount = (Integer) referenceMap.get(key);
		if (refCount == null) {
			result = true;
			refCount = new Integer(0);
		}
		referenceMap.put(key, new Integer(refCount.intValue() + 1));
		return result;
	}

	/**
	 * returns true if the given key has been removed
	 * 
	 * @param referenceMap
	 * @param key
	 * @return true if the ref count of the given key was 1
	 */
	private boolean decrementRefCount(Map referenceMap, Object key) {
		boolean result = false;
		Integer refCount = (Integer) referenceMap.get(key);
		Assert.isTrue(refCount != null);
		if (refCount.intValue() == 1) {
			referenceMap.remove(key);
			result = true;
		} else {
			referenceMap.put(key, new Integer(refCount.intValue() - 1));
		}
		return result;
	}

	// returns true if this model was removed from getModels();
	private boolean removeModel(Object source, Saveable model) {
		boolean result = false;
		Set modelsForSource = (Set) modelMap.get(source);
		if (modelsForSource == null) {
			logWarning(
					"Ignored attempt to remove a saveable when no saveables were known", source, model); //$NON-NLS-1$
		} else {
			if (modelsForSource.remove(model)) {
				result = decrementRefCount(modelRefCounts, model);
				if (modelsForSource.isEmpty()) {
					modelMap.remove(source);
				}
			} else {
				logWarning(
						"Ignored attempt to remove a saveable that was not registered", source, model); //$NON-NLS-1$
			}
		}
		return result;
	}

	private void logWarning(String message, Object source, Saveable model) {
		// create a new exception 
		AssertionFailedException assertionFailedException = new AssertionFailedException("unknown saveable: " + model //$NON-NLS-1$
				+ " from part: " + source); //$NON-NLS-1$
		// record the current stack trace to help with debugging
		assertionFailedException.fillInStackTrace();
		WorkbenchPlugin.log(StatusUtil.newStatus(IStatus.WARNING, message,
				assertionFailedException));
	}

	/**
	 * This implementation of handleModelLifecycleEvent must be called by
	 * implementers of ISaveablesSource whenever the list of models of the model
	 * source changes, or when the dirty state of models changes. The
	 * ISaveablesSource instance must be passed as the source of the event
	 * object.
	 * <p>
	 * This method may also be called by objects that hold on to models but are
	 * not workbench parts. In this case, the event source must be set to an
	 * object that is not an instanceof IWorkbenchPart.
	 * </p>
	 * <p>
	 * Corresponding open and close events must originate from the same
	 * (identical) event source.
	 * </p>
	 * <p>
	 * This method must be called on the UI thread.
	 * </p>
	 */
	public void handleLifecycleEvent(SaveablesLifecycleEvent event) {
		if (!(event.getSource() instanceof IWorkbenchPart)) {
			// just update the set of non-part sources. No prompting necessary.
			// See bug 139004.
			updateNonPartSource((ISaveablesSource) event.getSource());
			return;
		}
		Saveable[] modelArray = event.getSaveables();
		switch (event.getEventType()) {
		case SaveablesLifecycleEvent.POST_OPEN:
			addModels(event.getSource(), modelArray);
			break;
		case SaveablesLifecycleEvent.PRE_CLOSE:
			Saveable[] models = event.getSaveables();
			Map modelsDecrementing = new HashMap();
			Set modelsClosing = new HashSet();
			for (int i = 0; i < models.length; i++) {
				incrementRefCount(modelsDecrementing, models[i]);
			}

			fillModelsClosing(modelsClosing, modelsDecrementing);
			boolean canceled = promptForSavingIfNecessary(PlatformUI
					.getWorkbench().getActiveWorkbenchWindow(), modelsClosing, modelsDecrementing,
					!event.isForce());
			if (canceled) {
				event.setVeto(true);
			}
			break;
		case SaveablesLifecycleEvent.POST_CLOSE:
			removeModels(event.getSource(), modelArray);
			break;
		case SaveablesLifecycleEvent.DIRTY_CHANGED:
			fireModelLifecycleEvent(new SaveablesLifecycleEvent(this, event
					.getEventType(), event.getSaveables(), false));
			break;
		}
	}

	/**
	 * Updates the set of non-part saveables sources.
	 * @param source
	 */
	private void updateNonPartSource(ISaveablesSource source) {
		Saveable[] saveables = source.getSaveables();
		if (saveables.length == 0) {
			nonPartSources.remove(source);
		} else {
			nonPartSources.add(source);
		}
	}

	/**
	 * @param source
	 * @param modelArray
	 */
	private void removeModels(Object source, Saveable[] modelArray) {
		List removed = new ArrayList();
		for (int i = 0; i < modelArray.length; i++) {
			Saveable model = modelArray[i];
			if (removeModel(source, model)) {
				removed.add(model);
			}
		}
		if (removed.size() > 0) {
			fireModelLifecycleEvent(new SaveablesLifecycleEvent(this,
					SaveablesLifecycleEvent.POST_OPEN, (Saveable[]) removed
							.toArray(new Saveable[removed.size()]), false));
		}
	}

	/**
	 * @param source
	 * @param modelArray
	 */
	private void addModels(Object source, Saveable[] modelArray) {
		List added = new ArrayList();
		for (int i = 0; i < modelArray.length; i++) {
			Saveable model = modelArray[i];
			if (addModel(source, model)) {
				added.add(model);
			}
		}
		if (added.size() > 0) {
			fireModelLifecycleEvent(new SaveablesLifecycleEvent(this,
					SaveablesLifecycleEvent.POST_OPEN, (Saveable[]) added
							.toArray(new Saveable[added.size()]), false));
		}
	}

	/**
	 * @param event
	 */
	private void fireModelLifecycleEvent(SaveablesLifecycleEvent event) {
		Object[] listenerArray = listeners.getListeners();
		for (int i = 0; i < listenerArray.length; i++) {
			((ISaveablesLifecycleListener) listenerArray[i])
					.handleLifecycleEvent(event);
		}
	}

	/**
	 * Adds the given listener to the list of listeners. Has no effect if the
	 * same (identical) listener has already been added. The listener will be
	 * notified about changes to the models managed by this model manager. Event
	 * types include: <br>
	 * POST_OPEN when models were added to the list of models <br>
	 * POST_CLOSE when models were removed from the list of models <br>
	 * DIRTY_CHANGED when the dirty state of models changed
	 * <p>
	 * Listeners should ignore all other event types, including PRE_CLOSE. There
	 * is no guarantee that listeners are notified before models are closed.
	 * 
	 * @param listener
	 */
	public void addModelLifecycleListener(ISaveablesLifecycleListener listener) {
		listeners.add(listener);
	}

	/**
	 * Removes the given listener from the list of listeners. Has no effect if
	 * the given listener is not contained in the list.
	 * 
	 * @param listener
	 */
	public void removeModelLifecycleListener(ISaveablesLifecycleListener listener) {
		listeners.remove(listener);
	}

	/**
	 * @param partsToClose
	 * @param save
	 * @param window
	 * @return the post close info to be passed to postClose
	 */
	public Object preCloseParts(List partsToClose, boolean save,
			final IWorkbenchWindow window) {
		// reference count (how many occurrences of a model will go away?)
		PostCloseInfo postCloseInfo = new PostCloseInfo();
		for (Iterator it = partsToClose.iterator(); it.hasNext();) {
			IWorkbenchPart part = (IWorkbenchPart) it.next();
			postCloseInfo.partsClosing.add(part);
			if (part instanceof ISaveablePart) {
				ISaveablePart saveablePart = (ISaveablePart) part;
				if (save && !saveablePart.isSaveOnCloseNeeded()) {
					// pretend for now that this part is not closing
					continue;
				}
			}
			if (save && part instanceof ISaveablePart2) {
				ISaveablePart2 saveablePart2 = (ISaveablePart2) part;
				// TODO show saveablePart2 before prompting, see
				// EditorManager.saveAll
				int response = SaveableHelper.savePart(saveablePart2, window,
						true);
				if (response == ISaveablePart2.CANCEL) {
					// user canceled
					return null;
				} else if (response != ISaveablePart2.DEFAULT) {
					// only include this part in the following logic if it returned
					// DEFAULT
					continue;
				}
			}
			Saveable[] modelsFromSource = getSaveables(part);
			for (int i = 0; i < modelsFromSource.length; i++) {
				incrementRefCount(postCloseInfo.modelsDecrementing,
						modelsFromSource[i]);
			}
		}
		fillModelsClosing(postCloseInfo.modelsClosing,
				postCloseInfo.modelsDecrementing);
		if (save) {
			boolean canceled = promptForSavingIfNecessary(window,
					postCloseInfo.modelsClosing, postCloseInfo.modelsDecrementing, true);
			if (canceled) {
				return null;
			}
		}
		return postCloseInfo;
	}

	/**
	 * @param window
	 * @param modelsClosing
	 * @param canCancel
	 * @return true if the user canceled
	 */
	private boolean promptForSavingIfNecessary(final IWorkbenchWindow window,
			Set modelsClosing, Map modelsDecrementing, boolean canCancel) {
		List modelsToOptionallySave = new ArrayList();
		for (Iterator it = modelsDecrementing.keySet().iterator(); it.hasNext();) {
			Saveable modelDecrementing = (Saveable) it.next();
			if (modelDecrementing.isDirty() && !modelsClosing.contains(modelDecrementing)) {
				modelsToOptionallySave.add(modelDecrementing);
			}
		}
		
		boolean shouldCancel = modelsToOptionallySave.isEmpty() ? false : promptForSaving(modelsToOptionallySave,
				window, window, canCancel, true);
		
		if (shouldCancel) {
			return true;
		}

		List modelsToSave = new ArrayList();
		for (Iterator it = modelsClosing.iterator(); it.hasNext();) {
			Saveable modelClosing = (Saveable) it.next();
			if (modelClosing.isDirty()) {
				modelsToSave.add(modelClosing);
			}
		}
		return modelsToSave.isEmpty() ? false : promptForSaving(modelsToSave,
				window, window, canCancel, false);
	}

	/**
	 * @param modelsClosing
	 * @param modelsDecrementing
	 */
	private void fillModelsClosing(Set modelsClosing, Map modelsDecrementing) {
		for (Iterator it = modelsDecrementing.keySet().iterator(); it.hasNext();) {
			Saveable model = (Saveable) it.next();
			if (modelsDecrementing.get(model).equals(modelRefCounts.get(model))) {
				modelsClosing.add(model);
			}
		}
	}

	/**
	 * Prompt the user to save the given saveables.
	 * @param modelsToSave the saveables to be saved
	 * @param shellProvider the provider used to obtain a shell in prompting is
	 *            required. Clients can use a workbench window for this.
	 * @param runnableContext a runnable context that will be used to provide a
	 *            progress monitor while the save is taking place. Clients can
	 *            use a workbench window for this.
	 * @param canCancel whether the operation can be canceled
	 * @param stillOpenElsewhere whether the models are referenced by open parts
	 * @return true if the user canceled
	 */
	public boolean promptForSaving(List modelsToSave,
			final IShellProvider shellProvider, IRunnableContext runnableContext, final boolean canCancel, boolean stillOpenElsewhere) {
		// Save parts, exit the method if cancel is pressed.
		if (modelsToSave.size() > 0) {
			boolean canceled = SaveableHelper.waitForBackgroundSaveJobs(modelsToSave);
			if (canceled) {
				return true;
			}

			IPreferenceStore apiPreferenceStore = PrefUtil.getAPIPreferenceStore();
			boolean dontPrompt = stillOpenElsewhere && !apiPreferenceStore.getBoolean(IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN);

			if (dontPrompt) {
				modelsToSave.clear();
				return false;
			} else if (modelsToSave.size() == 1) {
				Saveable model = (Saveable) modelsToSave.get(0);
				// Show a dialog.
				String[] buttons;
				if(canCancel) {
					buttons = new String[] { IDialogConstants.YES_LABEL,
							IDialogConstants.NO_LABEL,
							IDialogConstants.CANCEL_LABEL };
				} else {
					buttons = new String[] { IDialogConstants.YES_LABEL,
							IDialogConstants.NO_LABEL};
				}

				// don't save if we don't prompt
				int choice = ISaveablePart2.NO;
				
				MessageDialog dialog;
				if (stillOpenElsewhere) {
					String message = NLS
							.bind(
									WorkbenchMessages.EditorManager_saveChangesOptionallyQuestion,
									model.getName());
					MessageDialogWithToggle dialogWithToggle = new MessageDialogWithToggle(shellProvider.getShell(),
							WorkbenchMessages.Save_Resource, null, message,
							MessageDialog.QUESTION, buttons, 0, WorkbenchMessages.EditorManager_closeWithoutPromptingOption, false) {
						protected int getShellStyle() {
							return (canCancel ? SWT.CLOSE : SWT.NONE)
									| SWT.TITLE | SWT.BORDER
									| SWT.APPLICATION_MODAL | SWT.SHEET
									| getDefaultOrientation();
						}
					};
					dialog = dialogWithToggle;
				} else {
					String message = NLS
							.bind(
									WorkbenchMessages.EditorManager_saveChangesQuestion,
									model.getName());
					dialog = new MessageDialog(shellProvider.getShell(),
							WorkbenchMessages.Save_Resource, null, message,
							MessageDialog.QUESTION, buttons, 0) {
						protected int getShellStyle() {
							return (canCancel ? SWT.CLOSE : SWT.NONE)
									| SWT.TITLE | SWT.BORDER
									| SWT.APPLICATION_MODAL | SWT.SHEET
									| getDefaultOrientation();
						}
					};
				}

				choice = SaveableHelper.testGetAutomatedResponse();
				if (SaveableHelper.testGetAutomatedResponse() == SaveableHelper.USER_RESPONSE) {
					choice = dialog.open();
					
					if(stillOpenElsewhere) {
						// map value of choice back to ISaveablePart2 values
						switch (choice) {
						case IDialogConstants.YES_ID:
							choice = ISaveablePart2.YES;
							break;
						case IDialogConstants.NO_ID:
							choice = ISaveablePart2.NO;
							break;
						case IDialogConstants.CANCEL_ID:
							choice = ISaveablePart2.CANCEL;
							break;
						default:
							break;
						}
						MessageDialogWithToggle dialogWithToggle = (MessageDialogWithToggle) dialog;
						if (choice != ISaveablePart2.CANCEL && dialogWithToggle.getToggleState()) {
							apiPreferenceStore.setValue(IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN, false);
						}
					}
				}

				// 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
					modelsToSave.clear();
					break;
				default:
				case ISaveablePart2.CANCEL: // cancel
					return true;
				}
			} else {
				MyListSelectionDialog dlg = new MyListSelectionDialog(
						shellProvider.getShell(),
						modelsToSave,
						new ArrayContentProvider(),
						new WorkbenchPartLabelProvider(),
						stillOpenElsewhere ? WorkbenchMessages.EditorManager_saveResourcesOptionallyMessage
								: WorkbenchMessages.EditorManager_saveResourcesMessage,
						canCancel, stillOpenElsewhere);
				dlg.setInitialSelections(modelsToSave.toArray());
				dlg.setTitle(EditorManager.SAVE_RESOURCES_TITLE);

				// this "if" statement aids in testing.
				if (SaveableHelper.testGetAutomatedResponse() == SaveableHelper.USER_RESPONSE) {
					int result = dlg.open();
					// Just return null to prevent the operation continuing
					if (result == IDialogConstants.CANCEL_ID)
						return true;

					if (dlg.getDontPromptSelection()) {
						apiPreferenceStore.setValue(IWorkbenchPreferenceConstants.PROMPT_WHEN_SAVEABLE_STILL_OPEN, false);
					}
					
					modelsToSave = Arrays.asList(dlg.getResult());
				}
			}
		}
		// Create save block.
		return saveModels(modelsToSave, shellProvider, runnableContext);
	}

	/**
	 * Save the given models.
	 * @param finalModels the list of models to be saved
	 * @param shellProvider the provider used to obtain a shell in prompting is
	 *            required. Clients can use a workbench window for this.
	 * @param runnableContext a runnable context that will be used to provide a
	 *            progress monitor while the save is taking place. Clients can
	 *            use a workbench window for this.
	 * @return <code>true</code> if the operation was canceled
	 */
	public boolean saveModels(final List finalModels, final IShellProvider shellProvider, IRunnableContext runnableContext) {
		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, true);
					if (monitorWrap.isCanceled())
						break;
				}
				monitorWrap.done();
			}
		};

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

	private static class PostCloseInfo {
		private List partsClosing = new ArrayList();

		private Map modelsDecrementing = new HashMap();

		private Set modelsClosing = new HashSet();
	}

	/**
	 * @param postCloseInfoObject
	 */
	public void postClose(Object postCloseInfoObject) {
		PostCloseInfo postCloseInfo = (PostCloseInfo) postCloseInfoObject;
		List removed = new ArrayList();
		for (Iterator it = postCloseInfo.partsClosing.iterator(); it.hasNext();) {
			IWorkbenchPart part = (IWorkbenchPart) it.next();
			Set saveables = (Set) modelMap.get(part);
			if (saveables != null) {
				// make a copy to avoid a ConcurrentModificationException - we
				// will remove from the original set as we iterate
				saveables = new HashSet(saveables);
				for (Iterator it2 = saveables.iterator(); it2.hasNext();) {
					Saveable saveable = (Saveable) it2.next();
					if (removeModel(part, saveable)) {
						removed.add(saveable);
					}
				}
			}
		}
		if (removed.size() > 0) {
			fireModelLifecycleEvent(new SaveablesLifecycleEvent(this,
					SaveablesLifecycleEvent.POST_CLOSE, (Saveable[]) removed
							.toArray(new Saveable[removed.size()]), false));
		}
	}

	/**
	 * 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 Saveable[] getSaveables(IWorkbenchPart part) {
		if (part instanceof ISaveablesSource) {
			ISaveablesSource source = (ISaveablesSource) part;
			return source.getSaveables();
		} else if (part instanceof ISaveablePart) {
			return new Saveable[] { new DefaultSaveable(part) };
		} else {
			return new Saveable[0];
		}
	}

	/**
	 * @param actualPart
	 */
	public void postOpen(IWorkbenchPart part) {
		addModels(part, getSaveables(part));
	}

	/**
	 * @param actualPart
	 */
	public void dirtyChanged(IWorkbenchPart part) {
		Saveable[] saveables = getSaveables(part);
		if (saveables.length > 0) {
			fireModelLifecycleEvent(new SaveablesLifecycleEvent(this,
					SaveablesLifecycleEvent.DIRTY_CHANGED, saveables, false));
		}
	}

	/**
	 * For testing purposes. Not to be called by clients.
	 * 
	 * @param model
	 * @return
	 */
	public Object[] testGetSourcesForModel(Saveable model) {
		List result = new ArrayList();
		for (Iterator it = modelMap.entrySet().iterator(); it.hasNext();) {
			Map.Entry entry = (Map.Entry) it.next();
			Set values = (Set) entry.getValue();
			if (values.contains(model)) {
				result.add(entry.getKey());
			}
		}
		return result.toArray();
	}

	private static final class MyListSelectionDialog extends
			ListSelectionDialog {
		private final boolean canCancel;
		private Button checkbox;
		private boolean dontPromptSelection;
		private boolean stillOpenElsewhere;

		private MyListSelectionDialog(Shell shell, Object input,
				IStructuredContentProvider contentprovider,
				ILabelProvider labelProvider, String message, boolean canCancel, boolean stillOpenElsewhere) {
			super(shell, input, contentprovider, labelProvider, message);
			this.canCancel = canCancel;
			this.stillOpenElsewhere = stillOpenElsewhere;
			int shellStyle = getShellStyle();
			if (!canCancel) {
				shellStyle &= ~SWT.CLOSE;
			}
			setShellStyle(shellStyle | SWT.SHEET);
		}

		/**
		 * @return
		 */
		public boolean getDontPromptSelection() {
			return dontPromptSelection;
		}

		protected void createButtonsForButtonBar(Composite parent) {
			createButton(parent, IDialogConstants.OK_ID,
					IDialogConstants.OK_LABEL, true);
			if (canCancel) {
				createButton(parent, IDialogConstants.CANCEL_ID,
						IDialogConstants.CANCEL_LABEL, false);
			}
		}
		
		protected Control createDialogArea(Composite parent) {
			 Composite dialogAreaComposite = (Composite) super.createDialogArea(parent);
			 
			 if (stillOpenElsewhere) {
				 Composite checkboxComposite = new Composite(dialogAreaComposite, SWT.NONE);
				 checkboxComposite.setLayout(new GridLayout(2, false));
				 
				 checkbox = new Button(checkboxComposite, SWT.CHECK);
				 checkbox.addSelectionListener(new SelectionAdapter() {
					public void widgetSelected(SelectionEvent e) {
						dontPromptSelection = checkbox.getSelection();
					}
				 });
				 GridData gd = new GridData();
				 gd.horizontalAlignment = SWT.BEGINNING;
				 checkbox.setLayoutData(gd);
				 
				 Label label = new Label(checkboxComposite, SWT.NONE);
				 label.setText(WorkbenchMessages.EditorManager_closeWithoutPromptingOption);
				 gd = new GridData();
				 gd.grabExcessHorizontalSpace = true;
				 gd.horizontalAlignment = SWT.BEGINNING;
			 }
			 
			 return dialogAreaComposite;
		}
	}

	/**
	 * @return a list of ISaveablesSource objects registered with this saveables
	 *         list which are not workbench parts.
	 */
	public ISaveablesSource[] getNonPartSources() {
		return (ISaveablesSource[]) nonPartSources
				.toArray(new ISaveablesSource[nonPartSources.size()]);
	}

	/**
	 * @param model
	 */
	public IWorkbenchPart[] getPartsForSaveable(Saveable model) {
		List result = new ArrayList();
		for (Iterator it = modelMap.entrySet().iterator(); it.hasNext();) {
			Map.Entry entry = (Map.Entry) it.next();
			Set values = (Set) entry.getValue();
			if (values.contains(model) && entry.getKey() instanceof IWorkbenchPart) {
				result.add(entry.getKey());
			}
		}
		return (IWorkbenchPart[]) result.toArray(new IWorkbenchPart[result.size()]);
	}

}
