Fix for Bug 112225 [WorkbenchParts] Need consistent save lifecycle when multiple parts share the same model
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IModelLifecycleListener.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IModelLifecycleListener.java
new file mode 100644
index 0000000..e17db1a
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/IModelLifecycleListener.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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;
+
+
+
+/**
+ * Listener for model lifecycle events.
+ * 
+ * @since 3.2
+ */
+public interface IModelLifecycleListener {
+	
+	/**
+	 * Handle the given model lifecycle event. 
+	 * @param event
+	 */
+	public void handleModelLifecycleEvent(ModelLifecycleEvent event);
+
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelManager.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelManager.java
new file mode 100644
index 0000000..d43c0f9
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelManager.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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;
+
+
+/**
+ * The model manager maintains a list of open saveable models.
+ * 
+ * @see SaveableModel
+ * @see ISaveableModelSource
+ * 
+ * @since 3.2
+ */
+public interface ISaveableModelManager extends IModelLifecycleListener {
+
+	/**
+	 * Returns the list of open models managed by this model manager.
+	 * 
+	 * @return a list of models
+	 */
+	public ISaveableModel[] getOpenModels();
+
+	/**
+	 * This implementation of handleModelLifecycleEvent must be called by
+	 * implementers of ISaveableModelSource whenever the list of models of the
+	 * model source changes, or when the dirty state of models changes. The
+	 * ISaveableModelSource 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 handleModelLifecycleEvent(ModelLifecycleEvent 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(IModelLifecycleListener 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(IModelLifecycleListener listener);
+
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelSource.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelSource.java
index 522da34..7ef1b3f 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelSource.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ISaveableModelSource.java
@@ -21,9 +21,18 @@
 public interface ISaveableModelSource {
 
 	/**
-	 * Returns the saveable models presented by the workbench part.
+	 * Returns the saveable models presented by the workbench part. If the
+	 * return value of this method changes during the lifetime of this part, the
+	 * model manager must be notified about these changes by calling
+	 * {@link ISaveableModelManager#handleModelLifecycleEvent(ModelLifecycleEvent)}.
+	 * <p>
+	 * The model manager is available as a service from the part site, by
+	 * calling <code>partSite.getService(ISaveableModelManager.class)</code>.
+	 * </p>
 	 * 
 	 * @return the saveable models presented by the workbench part
+	 * 
+	 * @see ISaveableModelManager
 	 */
 	ISaveableModel[] getModels();
 
@@ -31,7 +40,8 @@
 	 * Returns the saveable models currently active in the workbench part.
 	 * <p>
 	 * Certain workbench actions, such as Save, target only the active models in
-	 * the active part.
+	 * the active part. For example, the active saveable models could be
+	 * determined based on the current selection in the part.
 	 * </p>
 	 * 
 	 * @return the saveable models currently active in the workbench part
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ModelLifecycleEvent.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ModelLifecycleEvent.java
new file mode 100644
index 0000000..c14a196
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/ModelLifecycleEvent.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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;
+
+import java.util.EventObject;
+
+
+/**
+ * Event object describing a change to a set of ISaveableModel objects.
+ * 
+ * @since 3.2
+ */
+public class ModelLifecycleEvent extends EventObject {
+
+	/**
+	 * Serial version UID for this class.
+	 * <p>
+	 * Note: This class is not intended to be serialized.
+	 * </p>
+	 */
+	private static final long serialVersionUID = -3530773637989046452L;
+
+	/**
+	 * Event type constant specifying that the given models have been opened.
+	 */
+	public static final int POST_OPEN = 1;
+
+	/**
+	 * Event type constant specifying that the given models are about to be
+	 * closed. Listeners may veto the closing if isForce() is false.
+	 */
+	public static final int PRE_CLOSE = 2;
+
+	/**
+	 * Event type constant specifying that the given models have been closed.
+	 */
+	public static final int POST_CLOSE = 3;
+
+	/**
+	 * Event type constant specifying that the dirty state of the given models
+	 * has changed.
+	 */
+	public static final int DIRTY_CHANGED = 4;
+
+	private int eventType;
+
+	private ISaveableModel[] models;
+
+	private boolean force;
+
+	private boolean veto = false;
+
+	/**
+	 * Creates a new ModelLifecycleEvent.
+	 * 
+	 * @param source
+	 *            The source of the event. If an ISaveableModelSource notifies
+	 *            about changes to the models returned by
+	 *            {@link ISaveableModelSource#getModels()}, the source must be
+	 *            the ISaveableModelSource object.
+	 * @param eventType
+	 *            the event type, currently one of POST_OPEN, PRE_CLOSE,
+	 *            POST_CLOSE, DIRTY_CHANGED
+	 * @param models
+	 *            The affected models
+	 * @param force
+	 *            true if the event type is PRE_CLOSE and this is a closed force
+	 *            that cannot be canceled.
+	 */
+	public ModelLifecycleEvent(Object source, int eventType,
+			ISaveableModel[] models, boolean force) {
+		super(source);
+		this.eventType = eventType;
+		this.models = models;
+		this.force = force;
+	}
+
+	/**
+	 * Returns the eventType, currently one of POST_OPEN, PRE_CLOSE, POST_CLOSE,
+	 * DIRTY_CHANGED. Listeners should silently ignore unknown event types since
+	 * new event types might be added in the future.
+	 * 
+	 * @return the eventType
+	 */
+	public int getEventType() {
+		return eventType;
+	}
+
+	/**
+	 * Returns the affected models.
+	 * 
+	 * @return the models
+	 */
+	public ISaveableModel[] getModels() {
+		return models;
+	}
+
+	/**
+	 * Returns the veto. This value is ignored for POST_OPEN,POST_CLOSE, and
+	 * DIRTY_CHANGED.
+	 * 
+	 * @return Returns the veto.
+	 */
+	public boolean isVeto() {
+		return veto;
+	}
+
+	/**
+	 * @param veto
+	 *            The veto to set.
+	 */
+	public void setVeto(boolean veto) {
+		this.veto = veto;
+	}
+
+	/**
+	 * Sets the veto. This value is ignored for POST_OPEN, POST_CLOSE, and
+	 * DIRTY_CHANGED.
+	 * 
+	 * @return Returns the force.
+	 */
+	public boolean isForce() {
+		return force;
+	}
+
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java
index a2f682e..bbf0bc3 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/DefaultSaveableModel.java
@@ -97,4 +97,30 @@
 		return false;
 	}
 
+	/* (non-Javadoc)
+	 * @see java.lang.Object#hashCode()
+	 */
+	public int hashCode() {
+		return part.hashCode();
+	}
+
+	/* (non-Javadoc)
+	 * @see java.lang.Object#equals(java.lang.Object)
+	 */
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		final DefaultSaveableModel other = (DefaultSaveableModel) obj;
+		if (part == null) {
+			if (other.part != null)
+				return false;
+		} else if (!part.equals(other.part))
+			return false;
+		return true;
+	}
+
 }
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java
index d235162..392d3be 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/EditorManager.java
@@ -129,9 +129,9 @@
 	// Handler for the pin editor keyboard shortcut
 	private IHandlerActivation pinEditorHandlerActivation = null;
 
-	private static final String RESOURCES_TO_SAVE_MESSAGE = WorkbenchMessages.EditorManager_saveResourcesMessage;
+	static final String RESOURCES_TO_SAVE_MESSAGE = WorkbenchMessages.EditorManager_saveResourcesMessage;
 
-	private static final String SAVE_RESOURCES_TITLE = WorkbenchMessages.EditorManager_saveResourcesTitle;
+	static final String SAVE_RESOURCES_TITLE = WorkbenchMessages.EditorManager_saveResourcesTitle;
 
 	/**
 	 * EditorManager constructor comment.
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/PartList.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/PartList.java
index f695071..9394c0e 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/PartList.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/PartList.java
@@ -14,6 +14,7 @@
 import org.eclipse.ui.IEditorPart;
 import org.eclipse.ui.IEditorReference;
 import org.eclipse.ui.IPropertyListener;
+import org.eclipse.ui.ISaveableModelManager;
 import org.eclipse.ui.IWorkbenchPart;
 import org.eclipse.ui.IWorkbenchPartConstants;
 import org.eclipse.ui.IWorkbenchPartReference;
@@ -201,6 +202,10 @@
         // open event was fired or that a closed editor was somehow activated)
         Assert.isTrue(activeEditorReference != ref);
         
+        SaveableModelManager modelManager = (SaveableModelManager) actualPart
+				.getSite().getService(ISaveableModelManager.class);
+        modelManager.postOpen(actualPart);
+        
         // Fire the "part opened" event
         firePartOpened(ref);
     }
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java
index a8182d8..c197217 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ReferenceCounter.java
@@ -127,6 +127,18 @@
     }
 
     /**
+     * @param id is a unique ID for the object.
+     * @return the current ref count
+     */
+    public int getRef(Object id) {
+        RefRec rec = (RefRec) mapIdToRec.get(id);
+        if (rec == null) {
+			return 0;
+		}
+        return rec.refCount;
+    }
+
+    /**
      * Removes one reference from an object in the counter.
      * If the ref count drops to 0 the object is removed from
      * the counter completely.
@@ -135,17 +147,17 @@
      * @return the new ref count
      */
     public int removeRef(Object id) {
-        RefRec rec = (RefRec) mapIdToRec.get(id);
-        if (rec == null) {
-			return 0;
-		}
-        int newCount = rec.removeRef();
-        if (newCount <= 0) {
-			mapIdToRec.remove(id);
-		}
-        return newCount;
+    	RefRec rec = (RefRec) mapIdToRec.get(id);
+    	if (rec == null) {
+    		return 0;
+    	}
+    	int newCount = rec.removeRef();
+    	if (newCount <= 0) {
+    		mapIdToRec.remove(id);
+    	}
+    	return newCount;
     }
-
+    
     /**
      * Returns a complete list of the values in the counter.
      *
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/SaveableModelManager.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/SaveableModelManager.java
new file mode 100644
index 0000000..701083f
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/SaveableModelManager.java
@@ -0,0 +1,527 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IModelLifecycleListener;
+import org.eclipse.ui.ISaveableModel;
+import org.eclipse.ui.ISaveableModelManager;
+import org.eclipse.ui.ISaveableModelSource;
+import org.eclipse.ui.ISaveablePart;
+import org.eclipse.ui.ISaveablePart2;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.ModelLifecycleEvent;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.ListSelectionDialog;
+import org.eclipse.ui.internal.dialogs.EventLoopProgressMonitor;
+import org.eclipse.ui.model.WorkbenchPartLabelProvider;
+
+/**
+ * @since 3.2
+ * 
+ */
+public class SaveableModelManager implements ISaveableModelManager {
+
+	private ListenerList listeners = new ListenerList();
+
+	// event source (mostly ISaveableModelSource) -> Set of ISaveableModel
+	private Map modelMap = new HashMap();
+
+	// reference counting map, ISaveableModel -> Integer
+	private Map modelRefCounts = new HashMap();
+
+	public ISaveableModel[] getOpenModels() {
+		return (ISaveableModel[]) modelRefCounts.keySet().toArray(
+				new ISaveableModel[modelRefCounts.size()]);
+	}
+
+	// returns true if this model has not yet been in getModels()
+	private boolean addModel(Object source, ISaveableModel model) {
+		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);
+		}
+		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, ISaveableModel model) {
+		boolean result = false;
+		Set modelsForSource = (Set) modelMap.get(source);
+		if (modelsForSource == null) {
+			modelsForSource = new HashSet();
+			modelMap.put(source, modelsForSource);
+		}
+		if (modelsForSource.remove(model)) {
+			result = decrementRefCount(modelRefCounts, model);
+			if (modelsForSource.isEmpty()) {
+				modelMap.remove(source);
+			}
+		}
+		return result;
+	}
+
+	public void handleModelLifecycleEvent(ModelLifecycleEvent event) {
+		ISaveableModel[] modelArray = event.getModels();
+		switch (event.getEventType()) {
+		case ModelLifecycleEvent.POST_OPEN:
+			addModels(event.getSource(), modelArray);
+			break;
+		case ModelLifecycleEvent.PRE_CLOSE:
+			ISaveableModel[] models = event.getModels();
+			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,
+					!event.isForce());
+			if (canceled) {
+				event.setVeto(true);
+			}
+			break;
+		case ModelLifecycleEvent.POST_CLOSE:
+			removeModels(event.getSource(), modelArray);
+			break;
+		case ModelLifecycleEvent.DIRTY_CHANGED:
+			fireModelLifecycleEvent(new ModelLifecycleEvent(this, event
+					.getEventType(), event.getModels(), false));
+			break;
+		}
+	}
+
+	/**
+	 * @param source
+	 * @param modelArray
+	 */
+	private void removeModels(Object source, ISaveableModel[] modelArray) {
+		List removed = new ArrayList();
+		for (int i = 0; i < modelArray.length; i++) {
+			ISaveableModel model = modelArray[i];
+			if (removeModel(source, model)) {
+				removed.add(model);
+			}
+		}
+		if (removed.size() > 0) {
+			fireModelLifecycleEvent(new ModelLifecycleEvent(this,
+					ModelLifecycleEvent.POST_OPEN, (ISaveableModel[]) removed
+							.toArray(new ISaveableModel[removed.size()]), false));
+		}
+	}
+
+	/**
+	 * @param source
+	 * @param modelArray
+	 */
+	private void addModels(Object source, ISaveableModel[] modelArray) {
+		List added = new ArrayList();
+		for (int i = 0; i < modelArray.length; i++) {
+			ISaveableModel model = modelArray[i];
+			if (addModel(source, model)) {
+				added.add(model);
+			}
+		}
+		if (added.size() > 0) {
+			fireModelLifecycleEvent(new ModelLifecycleEvent(this,
+					ModelLifecycleEvent.POST_OPEN, (ISaveableModel[]) added
+							.toArray(new ISaveableModel[added.size()]), false));
+		}
+	}
+
+	/**
+	 * @param event
+	 */
+	private void fireModelLifecycleEvent(ModelLifecycleEvent event) {
+		Object[] listenerArray = listeners.getListeners();
+		for (int i = 0; i < listenerArray.length; i++) {
+			((IModelLifecycleListener) listenerArray[i])
+					.handleModelLifecycleEvent(event);
+		}
+	}
+
+	public void addModelLifecycleListener(IModelLifecycleListener listener) {
+		listeners.add(listener);
+	}
+
+	public void removeModelLifecycleListener(IModelLifecycleListener listener) {
+		listeners.remove(listener);
+	}
+
+	/**
+	 * @param editorsToClose
+	 * @param save
+	 * @param window
+	 * @return the post close info to be passed to postClose
+	 */
+	public Object preCloseParts(List editorsToClose, boolean save,
+			final IWorkbenchWindow window) {
+		// reference count (how many occurrences of a model will go away?)
+		PostCloseInfo postCloseInfo = new PostCloseInfo();
+		for (Iterator it = editorsToClose.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);
+				// only include this part in the following logic if it returned
+				// DEFAULT
+				if (response != ISaveablePart2.DEFAULT) {
+					continue;
+				}
+			}
+			ISaveableModel[] modelsFromSource = getSaveableModels(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, 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, boolean canCancel) {
+		// TODO prompt for saving of dirty modelsDecrementing but not closing
+		// (changes
+		// won't be lost)
+
+		List modelsToSave = new ArrayList();
+		for (Iterator it = modelsClosing.iterator(); it.hasNext();) {
+			ISaveableModel modelClosing = (ISaveableModel) it.next();
+			if (modelClosing.isDirty()) {
+				modelsToSave.add(modelClosing);
+			}
+		}
+		return modelsToSave.isEmpty() ? false : promptForSaving(modelsToSave,
+				window, canCancel);
+	}
+
+	/**
+	 * @param modelsClosing
+	 * @param modelsDecrementing
+	 */
+	private void fillModelsClosing(Set modelsClosing, Map modelsDecrementing) {
+		for (Iterator it = modelsDecrementing.keySet().iterator(); it.hasNext();) {
+			ISaveableModel model = (ISaveableModel) it.next();
+			if (modelsDecrementing.get(model).equals(modelRefCounts.get(model))) {
+				modelsClosing.add(model);
+			}
+		}
+	}
+
+	/**
+	 * @param modelsToSave
+	 * @param window
+	 * @param canCancel
+	 * @return true if the user canceled
+	 */
+	private boolean promptForSaving(List modelsToSave,
+			final IWorkbenchWindow window, boolean canCancel) {
+		// Save parts, exit the method if cancel is pressed.
+		if (modelsToSave.size() > 0) {
+			if (modelsToSave.size() == 1) {
+				ISaveableModel model = (ISaveableModel) 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(window.getShell(),
+						WorkbenchMessages.Save_Resource, null, message,
+						MessageDialog.QUESTION, buttons, 0);
+
+				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
+					modelsToSave.clear();
+					break;
+				default:
+				case ISaveablePart2.CANCEL: // cancel
+					return true;
+				}
+			} else {
+				ListSelectionDialog dlg = new MyListSelectionDialog(window
+						.getShell(), modelsToSave, new ArrayContentProvider(),
+						new WorkbenchPartLabelProvider(),
+						EditorManager.RESOURCES_TO_SAVE_MESSAGE, canCancel);
+				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;
+
+					modelsToSave = Arrays.asList(dlg.getResult());
+				}
+			}
+		}
+		// Create save block.
+		final List finalModels = modelsToSave;
+		IRunnableWithProgress progressOp = new IRunnableWithProgress() {
+			public void run(IProgressMonitor monitor) {
+				IProgressMonitor monitorWrap = new EventLoopProgressMonitor(
+						monitor);
+				monitorWrap.beginTask("", finalModels.size()); //$NON-NLS-1$
+				for (Iterator i = finalModels.iterator(); i.hasNext();) {
+					ISaveableModel model = (ISaveableModel) i.next();
+					// handle case where this model got saved as a result of
+					// saving another
+					if (!model.isDirty()) {
+						monitor.worked(1);
+						continue;
+					}
+					try {
+						model.doSave(new SubProgressMonitor(monitorWrap, 1));
+					} catch (CoreException e) {
+						ErrorDialog.openError(window.getShell(),
+								WorkbenchMessages.Error, e.getMessage(), e
+										.getStatus());
+					}
+					if (monitorWrap.isCanceled())
+						break;
+				}
+				monitorWrap.done();
+			}
+		};
+
+		// Do the save.
+		if (!SaveableHelper.runProgressMonitorOperation(
+				WorkbenchMessages.Save_All, progressOp, window)) {
+			// cancelled
+			return true;
+		}
+		return false;
+	}
+
+	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();
+			ISaveableModel[] modelArray = getSaveableModels(part);
+			for (int i = 0; i < modelArray.length; i++) {
+				ISaveableModel model = modelArray[i];
+				if (removeModel(part, model)) {
+					removed.add(model);
+				}
+			}
+		}
+		if (removed.size() > 0) {
+			fireModelLifecycleEvent(new ModelLifecycleEvent(this,
+					ModelLifecycleEvent.POST_CLOSE, (ISaveableModel[]) removed
+							.toArray(new ISaveableModel[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 ISaveableModel[] getSaveableModels(IWorkbenchPart part) {
+		if (part instanceof ISaveableModelSource) {
+			ISaveableModelSource source = (ISaveableModelSource) part;
+			return source.getModels();
+		} else if (part instanceof ISaveablePart) {
+			return new ISaveableModel[] { new DefaultSaveableModel(part) };
+		} else {
+			return new ISaveableModel[0];
+		}
+	}
+
+	/**
+	 * @param actualPart
+	 */
+	public void postOpen(IWorkbenchPart part) {
+		addModels(part, getSaveableModels(part));
+	}
+
+	/**
+	 * @param actualPart
+	 */
+	public void dirtyChanged(IWorkbenchPart part) {
+		ISaveableModel[] saveableModels = getSaveableModels(part);
+		if (saveableModels.length > 0) {
+			fireModelLifecycleEvent(new ModelLifecycleEvent(this,
+					ModelLifecycleEvent.DIRTY_CHANGED, saveableModels, false));
+		}
+	}
+
+	/**
+	 * For testing purposes. Not to be called by clients.
+	 * 
+	 * @param model
+	 * @return
+	 */
+	public Object[] testGetSourcesForModel(ISaveableModel 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 MyListSelectionDialog(Shell shell, Object input,
+				IStructuredContentProvider contentprovider,
+				ILabelProvider labelProvider, String message, boolean canCancel) {
+			super(shell, input, contentprovider, labelProvider, message);
+			this.canCancel = canCancel;
+			if (!canCancel) {
+				int shellStyle = getShellStyle();
+				shellStyle &= ~SWT.CLOSE;
+				setShellStyle(shellStyle);
+			}
+		}
+
+		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);
+			}
+		}
+	}
+
+}
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ViewFactory.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ViewFactory.java
index 98f519a..7f10892 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ViewFactory.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/ViewFactory.java
@@ -225,6 +225,17 @@
     }
 
     /**
+     * 
+     * @param viewRef
+     * @return the current reference count for the given view
+     */
+    public int getReferenceCount(IViewReference viewRef) {
+        String key = getKey(viewRef);
+        IViewReference ref = (IViewReference) counter.get(key);
+        return ref==null ? 0 : counter.getRef(key);
+    }
+    
+    /**
      * Releases an instance of a view.
      *
      * This factory does reference counting.  For more info see
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/Workbench.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/Workbench.java
index deab59b..b6b775e 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/Workbench.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/Workbench.java
@@ -85,6 +85,7 @@
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.IPerspectiveDescriptor;
 import org.eclipse.ui.IPerspectiveRegistry;
+import org.eclipse.ui.ISaveableModelManager;
 import org.eclipse.ui.ISaveablePart;
 import org.eclipse.ui.ISharedImages;
 import org.eclipse.ui.IWindowListener;
@@ -1210,10 +1211,14 @@
 	}
 
 	/**
-	 * Initializes all of the default command-based services for the workbench.
-	 * This also parses the registry and hooks up all the required listeners.
+	 * Initializes all of the default services for the workbench. For
+	 * initializing the command-based services, this also parses the registry
+	 * and hooks up all the required listeners.
 	 */
 	private final void initializeDefaultServices() {
+		
+		serviceLocator.registerService(ISaveableModelManager.class, new SaveableModelManager());
+		
 		/*
 		 * Phase 1 of the initialization of commands. When this phase completes,
 		 * all the services and managers will exist, and be accessible via the
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java
index 7e18e3a..4e59fcc 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPage.java
@@ -69,6 +69,7 @@
 import org.eclipse.ui.IPerspectiveDescriptor;
 import org.eclipse.ui.IPerspectiveRegistry;
 import org.eclipse.ui.IReusableEditor;
+import org.eclipse.ui.ISaveableModelManager;
 import org.eclipse.ui.ISaveablePart;
 import org.eclipse.ui.ISelectionListener;
 import org.eclipse.ui.IShowEditorInput;
@@ -1217,29 +1218,23 @@
         
         IEditorReference[] editorRefs = (IEditorReference[]) toClose.toArray(new IEditorReference[toClose.size()]);
         
-        if (save) { 
-            // Intersect the dirty editors with the editors that are closing
-            IEditorPart[] dirty = getDirtyEditors();
-            List intersect = new ArrayList();
-            for (int i = 0; i < editorRefs.length; i++) {
-                IEditorReference reference = editorRefs[i];
-                IEditorPart refPart = reference.getEditor(false);
-                if (refPart != null) {
-                    for (int j = 0; j < dirty.length; j++) {
-                        if (refPart.equals(dirty[j]) && refPart.isSaveOnCloseNeeded()) {
-                            intersect.add(refPart);
-                            break;
-                        }
-                    }
-                }
+        // notify the model manager before the close
+        List partsToClose = new ArrayList();
+        for (int i = 0; i < editorRefs.length; i++) {
+            IEditorPart refPart = editorRefs[i].getEditor(false);
+            if (refPart != null) {
+            	partsToClose.add(refPart);
             }
-            // Save parts, exit the method if cancel is pressed.
-            if (intersect.size() > 0) {
-                if (!EditorManager.saveAll(intersect, true, true,
-                        getWorkbenchWindow())) {
-					return false;
-				}
-            }
+        }
+        SaveableModelManager modelManager = null;
+        Object postCloseInfo = null;
+        if(partsToClose.size()>0) {
+        	modelManager = (SaveableModelManager) getWorkbenchWindow().getService(ISaveableModelManager.class);
+        	// this may prompt for saving and return null if the user canceled:
+        	postCloseInfo = modelManager.preCloseParts(partsToClose, save, getWorkbenchWindow());
+        	if (postCloseInfo==null) {
+        		return false;
+        	}
         }
 
         // Fire pre-removal changes 
@@ -1270,6 +1265,10 @@
         // Notify interested listeners after the close
         window.firePerspectiveChanged(this, getPerspective(),
                 CHANGE_EDITOR_CLOSE);
+        
+        if(modelManager!=null) {
+        	modelManager.postClose(postCloseInfo);
+        }
 
         // Return true on success.
         return true;
@@ -2113,6 +2112,24 @@
 			}
         }
         
+        int refCount = getViewFactory().getReferenceCount(ref);
+        SaveableModelManager saveableModelManager = null;
+        Object postCloseInfo = null;
+        if (refCount == 1) {
+        	IWorkbenchPart actualPart = ref.getPart(false);
+        	if (actualPart != null) {
+				saveableModelManager = (SaveableModelManager) actualPart
+						.getSite().getService(ISaveableModelManager.class);
+				postCloseInfo = saveableModelManager.preCloseParts(Collections
+						.singletonList(actualPart), true, this
+						.getWorkbenchWindow());
+				if (postCloseInfo==null) {
+					// cancel
+					return;
+				}
+			}
+        }
+        
         // Notify interested listeners before the hide
         window.firePerspectiveChanged(this, persp.getDesc(), ref,
                 CHANGE_VIEW_HIDE);
@@ -2127,6 +2144,10 @@
 
         // Notify interested listeners after the hide
         window.firePerspectiveChanged(this, getPerspective(), CHANGE_VIEW_HIDE);
+        
+        if (saveableModelManager != null) {
+        	saveableModelManager.postClose(postCloseInfo);
+        }
     }
 
     /* package */void refreshActiveView() {
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java
index 5ceb525..e461137 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchPartReference.java
@@ -24,6 +24,7 @@
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.IPropertyListener;
+import org.eclipse.ui.ISaveableModelManager;
 import org.eclipse.ui.ISaveablePart;
 import org.eclipse.ui.ISharedImages;
 import org.eclipse.ui.IWorkbenchPart;
@@ -272,7 +273,15 @@
             // Any other properties are just reported to listeners verbatim
             firePropertyChange(propId);
         }
-
+        
+        // Let the model manager know as well
+        if (propId == IWorkbenchPartConstants.PROP_DIRTY) {
+        	IWorkbenchPart actualPart = getPart(false);
+        	if (actualPart != null) {
+				SaveableModelManager modelManager = (SaveableModelManager) actualPart.getSite().getService(ISaveableModelManager.class);
+	        	modelManager.dirtyChanged(actualPart);
+        	}
+        }
     }
 
     /**