Bug 543442 - Workbench window will not prompt to save non
CompatibilityParts if compatibility parts are also being closed.

Previously if there were CompatibilityPart elements which were dirtied,
the dirtied nonCompatibilityParts were just ignored. So now This has
been fixed so that a single dialog pops up to save the dirtied
nonCompatibility part resources and dirtied CompatilityPart resources.

Also added tests to test the new API added in SaveablesList.

Change-Id: I0c6b498dca49b574eaa6fab3f1af73c03ffb0fc3
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/SaveablesList.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/SaveablesList.java
index 930a32e..8383041 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/SaveablesList.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/SaveablesList.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2006, 2017 IBM Corporation and others.
+ * Copyright (c) 2006, 2019 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -19,6 +19,7 @@
 import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -35,6 +36,7 @@
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.ListenerList;
 import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.e4.ui.workbench.modeling.ISaveHandler.Save;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.dialogs.MessageDialogWithToggle;
@@ -468,6 +470,27 @@
 	 * @param partsToClose
 	 * @param save
 	 * @param window
+	 * @param saveOptions
+	 * @return the post close info to be passed to postClose
+	 */
+	public Object preCloseParts(List<IWorkbenchPart> partsToClose, boolean save, final IWorkbenchWindow window,
+			Map<Saveable, Save> saveOptions) {
+		if (saveOptions == null || saveOptions.size() == 0) {
+			preCloseParts(partsToClose, save, window);
+		}
+		Collection<Save> saveValues = saveOptions.values();
+		for (Save decision : saveValues) {
+			if (decision == Save.CANCEL) {
+				return false;
+			}
+		}
+		return preCloseParts(partsToClose, false, save, window, window, saveOptions);
+	}
+
+	/**
+	 * @param partsToClose
+	 * @param save
+	 * @param window
 	 * @return the post close info to be passed to postClose
 	 */
 	public Object preCloseParts(List<IWorkbenchPart> partsToClose, boolean save,
@@ -482,6 +505,11 @@
 
 	public Object preCloseParts(List<IWorkbenchPart> partsToClose, boolean addNonPartSources, boolean save,
 			IShellProvider shellProvider, final IWorkbenchWindow window) {
+		return preCloseParts(partsToClose, addNonPartSources, save, shellProvider, window, null);
+	}
+
+	private Object preCloseParts(List<IWorkbenchPart> partsToClose, boolean addNonPartSources, boolean save,
+			IShellProvider shellProvider, final IWorkbenchWindow window, Map<Saveable, Save> saveOptions) {
 		// reference count (how many occurrences of a model will go away?)
 		PostCloseInfo postCloseInfo = new PostCloseInfo();
 		for (IWorkbenchPart part : partsToClose) {
@@ -493,12 +521,30 @@
 					continue;
 				}
 			}
+			Saveable[] saveables = getSaveables(part);
 			if (save && saveable instanceof ISaveablePart2) {
 				ISaveablePart2 saveablePart2 = (ISaveablePart2) saveable;
 				// TODO show saveablePart2 before prompting, see
 				// EditorManager.saveAll
-				int response = SaveableHelper.savePart(saveablePart2, window,
-						true);
+				boolean confirm = true;
+				int response = -2;
+				if (saveOptions != null) {
+					for (Saveable saveableKey : saveables) {
+						Save saveVal = saveOptions.get(saveableKey);
+						if (saveVal == Save.NO) {
+							confirm = true;
+							break;
+						} else if (saveVal == Save.CANCEL) {
+							response = ISaveablePart2.CANCEL;
+							break;
+						} else {
+							confirm = false;
+						}
+					}
+				}
+				if (response == -2) {
+					response = SaveableHelper.savePart(saveablePart2, window, confirm);
+				}
 				if (response == ISaveablePart2.CANCEL) {
 					// user canceled
 					return null;
@@ -507,13 +553,13 @@
 					// DEFAULT
 					continue;
 				}
+
 			}
-			for (Saveable saveableModel : getSaveables(part)) {
+			for (Saveable saveableModel : saveables) {
 				incrementRefCount(postCloseInfo.modelsDecrementing, saveableModel);
 			}
 		}
-		fillModelsClosing(postCloseInfo.modelsClosing,
-				postCloseInfo.modelsDecrementing);
+		fillModelsClosing(postCloseInfo.modelsClosing, postCloseInfo.modelsDecrementing);
 		if (addNonPartSources) {
 			for (ISaveablesSource nonPartSource : getNonPartSources()) {
 				Saveable[] saveables = nonPartSource.getSaveables();
@@ -526,7 +572,7 @@
 		}
 		if (save) {
 			boolean canceled = promptForSavingIfNecessary(shellProvider, window,
-					postCloseInfo.modelsClosing, postCloseInfo.modelsDecrementing, true);
+					postCloseInfo.modelsClosing, postCloseInfo.modelsDecrementing, true, saveOptions);
 			if (canceled) {
 				return null;
 			}
@@ -534,6 +580,20 @@
 		return postCloseInfo;
 	}
 
+	public Map<IWorkbenchPart, List<Saveable>> getSaveables(List<IWorkbenchPart> parts) {
+		Map<IWorkbenchPart, List<Saveable>> saveablesMap = null;
+		if (parts != null && parts.size() > 0) {
+			saveablesMap = new HashMap<>();
+			for (IWorkbenchPart part : parts) {
+				Saveable[] saveables = getSaveables(part);
+				if (saveables != null && saveables.length > 0) {
+					saveablesMap.put(part, Arrays.asList(saveables));
+				}
+			}
+		}
+		return saveablesMap;
+	}
+
 	/**
 	 * @param window
 	 * @param modelsClosing
@@ -543,12 +603,13 @@
 	private boolean promptForSavingIfNecessary(final IWorkbenchWindow window,
 			Set<Saveable> modelsClosing, Map<Saveable, Integer> modelsDecrementing, boolean canCancel) {
 		return promptForSavingIfNecessary(window, window, modelsClosing, modelsDecrementing,
-				canCancel);
+				canCancel, null);
 	}
 
 	private boolean promptForSavingIfNecessary(IShellProvider shellProvider,
  IWorkbenchWindow window,
-			Set<Saveable> modelsClosing, Map<Saveable, Integer> modelsDecrementing, boolean canCancel) {
+			Set<Saveable> modelsClosing, Map<Saveable, Integer> modelsDecrementing, boolean canCancel,
+			Map<Saveable, Save> saveOptionMap) {
 		List<Saveable> modelsToOptionallySave = new ArrayList<>();
 		for (Saveable modelDecrementing : modelsDecrementing.keySet()) {
 			if (modelDecrementing.isDirty() && !modelsClosing.contains(modelDecrementing)) {
@@ -557,7 +618,7 @@
 		}
 
 		boolean shouldCancel = modelsToOptionallySave.isEmpty() ? false : promptForSaving(
-				modelsToOptionallySave, shellProvider, window, canCancel, true);
+				modelsToOptionallySave, shellProvider, window, canCancel, true, saveOptionMap);
 
 		if (shouldCancel) {
 			return true;
@@ -570,7 +631,7 @@
 			}
 		}
 		return modelsToSave.isEmpty() ? false : promptForSaving(modelsToSave, shellProvider,
-				window, canCancel, false);
+				window, canCancel, false, saveOptionMap);
 	}
 
 	/**
@@ -586,6 +647,23 @@
 		}
 	}
 
+	private boolean promptForSaving(List<Saveable> modelsToSave, final IShellProvider shellProvider,
+			IRunnableContext runnableContext, final boolean canCancel, boolean stillOpenElsewhere,
+			Map<Saveable, Save> saveOptionMap) {
+		List<Saveable> tobeSaved = new ArrayList<>();
+		if (saveOptionMap == null || saveOptionMap.size() == 0) {
+			return promptForSaving(modelsToSave, shellProvider, runnableContext, canCancel, stillOpenElsewhere);
+		}
+		if (modelsToSave.size() > 0) {
+			for (Saveable saveable : modelsToSave) {
+				Save option = saveOptionMap.get(saveable);
+				if (option != null && option == Save.YES) {
+					tobeSaved.add(saveable);
+				}
+			}
+		}
+		return saveModels(tobeSaved, shellProvider, runnableContext);
+	}
 	/**
 	 * Prompt the user to save the given saveables.
 	 * @param modelsToSave the saveables to be saved
diff --git a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchWindow.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchWindow.java
index 15cd392..be5e728 100644
--- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchWindow.java
+++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/WorkbenchWindow.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2018 IBM Corporation and others.
+ * Copyright (c) 2000, 2019 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -31,6 +31,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -157,6 +158,7 @@
 import org.eclipse.ui.IWorkbenchPreferenceConstants;
 import org.eclipse.ui.IWorkbenchWindow;
 import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.Saveable;
 import org.eclipse.ui.WorkbenchException;
 import org.eclipse.ui.application.ActionBarAdvisor;
 import org.eclipse.ui.application.WorkbenchAdvisor;
@@ -197,6 +199,7 @@
 import org.eclipse.ui.keys.IBindingService;
 import org.eclipse.ui.menus.CommandContributionItem;
 import org.eclipse.ui.menus.IMenuService;
+import org.eclipse.ui.model.WorkbenchPartLabelProvider;
 import org.eclipse.ui.services.IDisposable;
 import org.eclipse.ui.services.IEvaluationService;
 import org.eclipse.ui.services.IServiceScopes;
@@ -605,9 +608,114 @@
 					return super.save(dirtyPart, confirm);
 				}
 
+				private boolean saveParts(ArrayList<MPart> dirtyParts, Save[] decisions) {
+					if (decisions == null || decisions.length == 0) {
+						super.saveParts(dirtyParts, true);
+					}
+					if (dirtyParts.size() != decisions.length) {
+						for (Save decision : decisions) {
+							if (decision == Save.CANCEL) {
+								return false;
+							}
+						}
+					}
+					List<MPart> dirtyPartsList = Collections.unmodifiableList(new ArrayList<>(dirtyParts));
+					for (Save decision : decisions) {
+						if (decision == Save.CANCEL) {
+							return false;
+						}
+					}
+
+					for (int i = 0; i < decisions.length; i++) {
+						if (decisions[i] == Save.YES) {
+							if (!save(dirtyPartsList.get(i), false)) {
+								return false;
+							}
+						}
+					}
+					return true;
+				}
+
+				private boolean saveMixedParts(ArrayList<MPart> nonCompParts, ArrayList<IWorkbenchPart> compParts,
+						boolean confirm) {
+					SaveablesList saveablesList = (SaveablesList) PlatformUI.getWorkbench()
+							.getService(ISaveablesLifecycleListener.class);
+					if (!confirm) {
+						boolean saved = super.saveParts(nonCompParts, confirm);
+						Object saveResult = saveablesList.preCloseParts(compParts, true, WorkbenchWindow.this);
+						return ((saveResult != null) && saved);
+					}
+					LabelProvider labelProvider = new LabelProvider() {
+						WorkbenchPartLabelProvider workbenchLabelProvider = new WorkbenchPartLabelProvider();
+						@Override
+						public String getText(Object element) {
+							if (element instanceof Saveable) {
+								return workbenchLabelProvider.getText(element);
+							}
+							return ((MPart) element).getLocalizedLabel();
+						}
+					};
+					ArrayList<Object> listParts = new ArrayList<>();
+					Map<IWorkbenchPart, List<Saveable>> saveableMap = saveablesList.getSaveables(compParts);
+					listParts.addAll(nonCompParts);
+					LinkedHashSet<Saveable> saveablesSet = new LinkedHashSet<>();
+					for (IWorkbenchPart workbenchPart : compParts) {
+						List<Saveable> list = saveableMap.get(workbenchPart);
+						if (list != null) {
+							saveablesSet.addAll(list);
+						}
+					}
+					listParts.addAll(saveablesSet);
+					ListSelectionDialog dialog = new ListSelectionDialog(getShell(), listParts,
+							ArrayContentProvider.getInstance(), labelProvider,
+							WorkbenchMessages.EditorManager_saveResourcesMessage);
+					dialog.setInitialSelections(listParts.toArray());
+					dialog.setTitle(WorkbenchMessages.EditorManager_saveResourcesTitle);
+					if (dialog.open() == IDialogConstants.CANCEL_ID) {
+						return false;
+					}
+
+					Object[] toSave = dialog.getResult();
+					Save[] nonCompatSaves = new Save[nonCompParts.size()];
+					Save[] compatSaves = new Save[compParts.size()];
+					Arrays.fill(nonCompatSaves, Save.NO);
+					Arrays.fill(compatSaves, Save.NO);
+					for (int i = 0; i < nonCompatSaves.length; i++) {
+						MPart part = nonCompParts.get(i);
+						for (Object o : toSave) {
+							if (o == part) {
+								nonCompatSaves[i] = Save.YES;
+								break;
+							}
+						}
+					}
+					Map<Saveable, Save> saveOptionMap = new HashMap<>();
+					for (Saveable saveable: saveablesSet) {
+						boolean found = false;
+						for (Object o : toSave) {
+							if (o == saveable) {
+								saveOptionMap.put(saveable, Save.YES);
+								found = true;
+								break;
+							}
+						}
+						if (!found) {
+							saveOptionMap.put(saveable, Save.NO);
+						}
+					}
+					boolean saved = saveParts(nonCompParts, nonCompatSaves);
+					if (!saved) {
+						return saved;
+					}
+					Object saveResult = saveablesList.preCloseParts(compParts, true, WorkbenchWindow.this,
+							saveOptionMap);
+					return ((saveResult != null) && saved);
+				}
+
 				@Override
 				public boolean saveParts(Collection<MPart> dirtyParts, boolean confirm) {
 					ArrayList<IWorkbenchPart> saveableParts = new ArrayList<>();
+					ArrayList<MPart> nonCompatibilityParts = new ArrayList<>();
 					for (MPart part : dirtyParts) {
 						Object object = part.getObject();
 						if (object instanceof CompatibilityPart) {
@@ -615,17 +723,21 @@
 							if (SaveableHelper.isSaveable(workbenchPart)) {
 								saveableParts.add(workbenchPart);
 							}
+						} else {
+							nonCompatibilityParts.add(part);
 						}
 					}
 					if (saveableParts.isEmpty()) {
 						return super.saveParts(dirtyParts, confirm);
+					} else if (!nonCompatibilityParts.isEmpty()) {
+						return saveMixedParts(nonCompatibilityParts, saveableParts, confirm);
 					}
 
 					SaveablesList saveablesList = (SaveablesList) PlatformUI.getWorkbench()
 							.getService(ISaveablesLifecycleListener.class);
 					Object saveResult = saveablesList.preCloseParts(saveableParts, true,
 							WorkbenchWindow.this);
-					return saveResult != null;
+					return (saveResult != null);
 				}
 			};
 			localSaveHandler.logger = logger;
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/ApiTestSuite.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/ApiTestSuite.java
index 2d4a3d4..c89e11c 100644
--- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/ApiTestSuite.java
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/ApiTestSuite.java
@@ -76,7 +76,8 @@
 	 DependencyInjectionViewTest.class,
 	 Bug407422Test.class,
 	 MultipleWindowsTest.class,
-	 Bug543609Test.class
+	 Bug543609Test.class,
+	 SaveablesListTest.class
 })
 public class ApiTestSuite {
 
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/SaveablesListTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/SaveablesListTest.java
new file mode 100644
index 0000000..374ed40
--- /dev/null
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/api/SaveablesListTest.java
@@ -0,0 +1,211 @@
+/*******************************************************************************
+ * Copyright (c) 2019 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.tests.api;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.e4.ui.workbench.modeling.ISaveHandler.Save;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.ISaveablesLifecycleListener;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.Saveable;
+import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.internal.SaveablesList;
+import org.eclipse.ui.tests.harness.util.CallHistory;
+import org.eclipse.ui.tests.harness.util.FileUtil;
+import org.eclipse.ui.tests.harness.util.UITestCase;
+
+/**
+ * @since 3.5
+ *
+ */
+public class SaveablesListTest extends UITestCase {
+
+	private IWorkbenchWindow fWin;
+
+	private IProject proj;
+
+	private IWorkbenchPage page;
+
+	public SaveablesListTest(String testName) {
+		super(testName);
+	}
+
+	@Override
+	protected void doSetUp() throws Exception {
+		super.doSetUp();
+		fWin = openTestWindow();
+		page = openTestPage(fWin);
+	}
+
+	@Override
+	protected void doTearDown() throws Exception {
+		if (proj != null) {
+			FileUtil.deleteProject(proj);
+			proj = null;
+		}
+		if (page != null) {
+			page.closeAllEditors(false);
+			page.close();
+		}
+		if (fWin != null) {
+			fWin.close();
+		}
+		super.doTearDown();
+	}
+
+	public void testPreclosePartsWithSaveOptions_SaveAll() throws Throwable {
+		int total = 5;
+		final IFile[] files = new IFile[total];
+		IEditorPart[] editors = new IEditorPart[total];
+		CallHistory[] callTraces = new CallHistory[total];
+		MockEditorPart[] mocks = new MockEditorPart[total];
+		List<IWorkbenchPart> parts = new ArrayList<>();
+
+		proj = FileUtil.createProject("testOpenEditor");
+		for (int i = 0; i < total; i++) {
+			files[i] = FileUtil.createFile(i + ".mock2", proj);
+		}
+
+		SaveablesList saveablesList = (SaveablesList) PlatformUI.getWorkbench()
+				.getService(ISaveablesLifecycleListener.class);
+
+		for (int i = 0; i < total; i++) {
+			editors[i] = IDE.openEditor(page, files[i]);
+			mocks[i] = (MockEditorPart) editors[i];
+			mocks[i].setDirty(true);
+			callTraces[i] = mocks[i].getCallHistory();
+			parts.add(editors[i]);
+		}
+		Map<IWorkbenchPart, List<Saveable>> saveableMap = saveablesList.getSaveables(parts);
+		Map<Saveable, Save> map = new LinkedHashMap<>();
+		for (IWorkbenchPart part : parts) {
+			List<Saveable> saveables = saveableMap.get(part);
+			if (saveables != null) {
+				for (Saveable saveable : saveables) {
+					map.put(saveable, Save.YES);
+				}
+			}
+		}
+		assertEquals((saveablesList.preCloseParts(parts, true, fWin, map) != null), true);
+		for (int i = 0; i < total; i++) {
+			assertEquals(callTraces[i].contains("isDirty"), true);
+			assertEquals(callTraces[i].contains("doSave"), true);
+			callTraces[i].clear();
+		}
+	}
+
+	public void testPreclosePartsWithSaveOptions_DiscardAll() throws Throwable {
+		int total = 5;
+		final IFile[] files = new IFile[total];
+		IEditorPart[] editors = new IEditorPart[total];
+		CallHistory[] callTraces = new CallHistory[total];
+		MockEditorPart[] mocks = new MockEditorPart[total];
+		List<IWorkbenchPart> parts = new ArrayList<>();
+
+		proj = FileUtil.createProject("testOpenEditor");
+		for (int i = 0; i < total; i++) {
+			files[i] = FileUtil.createFile(i + ".mock2", proj);
+		}
+
+		SaveablesList saveablesList = (SaveablesList) PlatformUI.getWorkbench()
+				.getService(ISaveablesLifecycleListener.class);
+
+		for (int i = 0; i < total; i++) {
+			editors[i] = IDE.openEditor(page, files[i]);
+			mocks[i] = (MockEditorPart) editors[i];
+			mocks[i].setDirty(true);
+			callTraces[i] = mocks[i].getCallHistory();
+			parts.add(editors[i]);
+		}
+		Map<IWorkbenchPart, List<Saveable>> saveableMap = saveablesList.getSaveables(parts);
+		Map<Saveable, Save> map = new LinkedHashMap<>();
+
+		for (IWorkbenchPart part : parts) {
+			List<Saveable> saveables = saveableMap.get(part);
+			if (saveables != null) {
+				for (Saveable saveable : saveables) {
+					map.put(saveable, Save.NO);
+				}
+			}
+		}
+		assertEquals((saveablesList.preCloseParts(parts, true, fWin, map) != null), true);
+		for (int i = 0; i < total; i++) {
+			assertEquals(callTraces[i].contains("isDirty"), true);
+			assertEquals(callTraces[i].contains("doSave"), false);
+		}
+	}
+
+	public void testPreclosePartsWithSaveOptions_SaveFew() throws Throwable {
+		int total = 5;
+		final IFile[] files = new IFile[total];
+		IEditorPart[] editors = new IEditorPart[total];
+		CallHistory[] callTraces = new CallHistory[total];
+		MockEditorPart[] mocks = new MockEditorPart[total];
+		List<IWorkbenchPart> parts = new ArrayList<>();
+
+		proj = FileUtil.createProject("testOpenEditor");
+		for (int i = 0; i < total; i++) {
+			files[i] = FileUtil.createFile(i + ".mock2", proj);
+		}
+
+		SaveablesList saveablesList = (SaveablesList) PlatformUI.getWorkbench()
+				.getService(ISaveablesLifecycleListener.class);
+
+		for (int i = 0; i < total; i++) {
+			editors[i] = IDE.openEditor(page, files[i]);
+			mocks[i] = (MockEditorPart) editors[i];
+			if (i % 2 == 0) {
+				mocks[i].setDirty(true);
+			}
+			callTraces[i] = mocks[i].getCallHistory();
+			parts.add(editors[i]);
+		}
+		Map<IWorkbenchPart, List<Saveable>> saveableMap = saveablesList.getSaveables(parts);
+		Map<Saveable, Save> map = new LinkedHashMap<>();
+		int j = 0;
+		for (IWorkbenchPart part : parts) {
+			List<Saveable> saveables = saveableMap.get(part);
+			if (saveables != null) {
+				for (Saveable saveable : saveables) {
+					if (j % 2 == 0) {
+						map.put(saveable, Save.YES);
+					} else {
+						map.put(saveable, Save.NO);
+					}
+				}
+			}
+			j++;
+		}
+		assertEquals((saveablesList.preCloseParts(parts, true, fWin, map) != null), true);
+		for (int i = 0; i < total; i++) {
+			if (i % 2 == 0) {
+				assertEquals(callTraces[i].contains("isDirty"), true);
+				assertEquals(callTraces[i].contains("doSave"), true);
+			} else {
+				assertEquals(callTraces[i].contains("isDirty"), true);
+				assertEquals(callTraces[i].contains("doSave"), false);
+			}
+
+		}
+	}
+}