Bug 176764 - [Undo] - DeleteResourcesOperation does not handle overlapped selections
diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/AbstractCopyOrMoveResourcesOperation.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/AbstractCopyOrMoveResourcesOperation.java
index 3238f65..49c0358 100644
--- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/AbstractCopyOrMoveResourcesOperation.java
+++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/AbstractCopyOrMoveResourcesOperation.java
@@ -46,7 +46,8 @@
 	 * 
 	 * @param resources
 	 *            the resources to be moved or copied.  May not contain null
-	 *            resources.
+	 *            resources, or resources that are descendants of already 
+	 *            included resources.
 	 * @param destinationPaths
 	 *            the destination paths for the resources, including the name to
 	 *            be assigned to the resource at its new location.  May not contain
@@ -58,13 +59,20 @@
 	AbstractCopyOrMoveResourcesOperation(IResource[] resources,
 			IPath[] destinationPaths, String label) {
 		super(resources, label);
-		if (resources == null || destinationPaths == null)
+		// Check for null arguments
+		if (this.resources == null || destinationPaths == null)
 			throw new IllegalArgumentException("The resource and destination paths may not be null"); //$NON-NLS-1$
-		if (resources.length != destinationPaths.length) {
+		// Special case to flag descendants.  Note this would fail on the next check
+		// anyway, so we are first giving a more specific explanation.
+		// See bug #176764
+		if (this.resources.length != resources.length)
+			throw new IllegalArgumentException("The resource list contained descendants that cannot be moved to separate destination paths"); //$NON-NLS-1$
+		// Check for destination paths corresponding for each resource
+		if (this.resources.length != destinationPaths.length) {
 			throw new IllegalArgumentException("The resource and destination paths must be the same length"); //$NON-NLS-1$
 		}
-		for (int i=0; i<resources.length; i++) {
-			if (resources[i] == null) {
+		for (int i=0; i<this.resources.length; i++) {
+			if (this.resources[i] == null) {
 				throw new IllegalArgumentException("The resources array may not contain null resources"); //$NON-NLS-1$
 			}
 			if (destinationPaths[i] == null) {
diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/AbstractResourcesOperation.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/AbstractResourcesOperation.java
index d58d388..542104f 100644
--- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/AbstractResourcesOperation.java
+++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/AbstractResourcesOperation.java
@@ -11,6 +11,9 @@
 
 package org.eclipse.ui.ide.undo;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IAdaptable;
@@ -42,6 +45,16 @@
 	 * restore overwritten resources.
 	 */
 	protected ResourceDescription[] resourceDescriptions;
+	
+	/*
+	 * Return true if the specified subResource is a descendant of the specified
+	 * super resource.  Used to remove descendants from the resource array when
+	 * an operation is requested on a parent and its descendant.
+	 */
+	private static boolean isDescendantOf(IResource subResource, IResource superResource) {
+		return ! subResource.equals(superResource) && superResource.getFullPath().isPrefixOf(subResource.getFullPath());
+	}
+
 
 	/**
 	 * Create an Abstract Resources Operation
@@ -276,4 +289,33 @@
 		return MultiRule.combine(ruleArray);
 
 	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#setTargetResources(org.eclipse.core.resources.IResource[])
+	 */
+	protected void setTargetResources(IResource[] resources) {
+		// Remove any descendants if the parent has also
+		// been specified.
+		List subResources = new ArrayList();
+		for (int i = 0; i < resources.length; i++) {
+			IResource subResource = resources[i];
+			for (int j = 0; j < resources.length; j++) {
+				IResource superResource = resources[j];
+				if (isDescendantOf(subResource, superResource))
+					subResources.add(subResource);
+			}
+		}
+		IResource[] nestedResourcesRemoved = new IResource[resources.length
+				- subResources.size()];
+		int j = 0;
+		for (int i = 0; i < resources.length; i++) {
+			if (!subResources.contains(resources[i])) {
+				nestedResourcesRemoved[j] = resources[i];
+				j++;
+			}
+		}
+		super.setTargetResources(nestedResourcesRemoved);
+	}
 }
diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/CopyResourcesOperation.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/CopyResourcesOperation.java
index fb914f3..2df1426 100644
--- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/CopyResourcesOperation.java
+++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/ide/undo/CopyResourcesOperation.java
@@ -83,7 +83,7 @@
 	public CopyResourcesOperation(IResource[] resources, IPath destinationPath,
 			String label) {
 		super(resources, destinationPath, label);
-		setOriginalResources(resources);
+		setOriginalResources(this.resources);
 	}
 
 	/**
@@ -105,7 +105,7 @@
 	public CopyResourcesOperation(IResource[] resources,
 			IPath[] destinationPaths, String label) {
 		super(resources, destinationPaths, label);
-		setOriginalResources(resources);
+		setOriginalResources(this.resources);
 	}
 
 	/*
diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/operations/WorkspaceOperationsTests.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/operations/WorkspaceOperationsTests.java
index 6249b12..6cf0664 100644
--- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/operations/WorkspaceOperationsTests.java
+++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/operations/WorkspaceOperationsTests.java
@@ -1243,6 +1243,56 @@
 		assertTrue("Folder content was altered", snap.isValid(targetProject));
 	}
 
+	public void testRedundantSubFolderMoveUndoRedo() throws ExecutionException,
+			CoreException {
+		IPath targetPath = targetProject.getFullPath();
+		IPath targetPathWithName = targetPath.append(testFolder.getName());
+		MoveResourcesOperation op = new MoveResourcesOperation(new IResource[] {
+				testFolder, testSubFolder }, targetPath,
+				"testRedundantSubFolderMove");
+		FolderSnapshot snap = new FolderSnapshot(testFolder);
+		execute(op);
+		IFolder movedFolder = getWorkspaceRoot().getFolder(targetPathWithName);
+		assertTrue("Folder move failed", movedFolder.exists());
+		assertTrue("Folder content was altered", snap.isValid(targetProject));
+
+		undo();
+		movedFolder = getWorkspaceRoot().getFolder(targetPathWithName);
+		assertFalse("Move undo failed", movedFolder.exists());
+		assertTrue("Folder content was altered on undo", snap
+				.isValid(testProject));
+
+		redo();
+		movedFolder = getWorkspaceRoot().getFolder(targetPathWithName);
+		assertTrue("Folder move failed", movedFolder.exists());
+		assertTrue("Folder content was altered", snap.isValid(targetProject));
+	}
+
+	public void testRedundantFolderFileMoveUndoRedo()
+			throws ExecutionException, CoreException {
+		IPath targetPath = targetProject.getFullPath();
+		IPath targetPathWithName = targetPath.append(testFolder.getName());
+		MoveResourcesOperation op = new MoveResourcesOperation(new IResource[] {
+				testFolder, testFileWithContent }, targetPath,
+				"testRedundantFolderFileMove");
+		FolderSnapshot snap = new FolderSnapshot(testFolder);
+		execute(op);
+		IFolder movedFolder = getWorkspaceRoot().getFolder(targetPathWithName);
+		assertTrue("Folder move failed", movedFolder.exists());
+		assertTrue("Folder content was altered", snap.isValid(targetProject));
+
+		undo();
+		movedFolder = getWorkspaceRoot().getFolder(targetPathWithName);
+		assertFalse("Move undo failed", movedFolder.exists());
+		assertTrue("Folder content was altered on undo", snap
+				.isValid(testProject));
+
+		redo();
+		movedFolder = getWorkspaceRoot().getFolder(targetPathWithName);
+		assertTrue("Folder move failed", movedFolder.exists());
+		assertTrue("Folder content was altered", snap.isValid(targetProject));
+	}
+
 	public void testFolderCopyUndoRedo() throws ExecutionException,
 			CoreException {
 		// copying with same name to a new project
@@ -1354,6 +1404,41 @@
 		assertFalse("Redo delete failed", testSubFolder.exists());
 	}
 
+	public void testNestedRedundantFolderDeleteUndoRedo()
+			throws ExecutionException, CoreException {
+		DeleteResourcesOperation op = new DeleteResourcesOperation(
+				new IResource[] { testFolder, testSubFolder },
+				"testNestedRedundantFolderDelete", false);
+		FolderSnapshot snap = new FolderSnapshot(testFolder);
+		execute(op);
+		assertFalse("Folder delete failed", testFolder.exists());
+		undo();
+		assertTrue("Folder recreation failed", testFolder.exists());
+		assertTrue("SubFolder recreation failed", testSubFolder.exists());
+		assertTrue("Folder content was altered on undo", snap
+				.isValid(testFolder.getParent()));
+		redo();
+		assertFalse("Redo delete failed", testFolder.exists());
+	}
+
+	public void testNestedRedundantFileDeleteUndoRedo()
+			throws ExecutionException, CoreException {
+		DeleteResourcesOperation op = new DeleteResourcesOperation(
+				new IResource[] { testFolder, testFileWithContent },
+				"testNestedRedundantFileDelete", false);
+		FolderSnapshot snap = new FolderSnapshot(testFolder);
+		execute(op);
+		assertFalse("Folder delete failed", testFolder.exists());
+		undo();
+		assertTrue("Folder recreation failed", testFolder.exists());
+		assertTrue("SubFolder recreation failed", testSubFolder.exists());
+		assertTrue("File recreation failed", testFileWithContent.exists());
+		assertTrue("Folder content was altered on undo", snap
+				.isValid(testFolder.getParent()));
+		redo();
+		assertFalse("Redo delete failed", testFolder.exists());
+	}
+
 	public void testFolderDeleteLinkedUndoRedo() throws ExecutionException,
 			CoreException {
 		DeleteResourcesOperation op = new DeleteResourcesOperation(
@@ -1844,6 +1929,42 @@
 				.isValid(targetProject));
 	}
 
+	public void testRedundantFileAndFolderCopy() throws CoreException,
+			ExecutionException {
+		// copying a file which is a child of a folder, keeping same name to a new project
+		CopyResourcesOperation op = new CopyResourcesOperation(new IResource[] {
+				testFolder, testFileWithContent }, targetProject
+				.getFullPath(), "testRedundantFileAndFolderCopy");
+		FolderSnapshot snapFolder = new FolderSnapshot(testFolder);
+		FileSnapshot snapFile = new FileSnapshot(testFileWithContent);
+		execute(op);
+		IFolder copiedFolder = targetProject.getFolder(testFolder.getName());
+		assertTrue("Folder copy failed", copiedFolder.exists());
+		assertTrue("Source folder was altered", snapFolder.isValid(testProject));
+		assertTrue("Folder copy does not match", snapFolder
+				.isValid(targetProject));
+		IFile copiedFile = targetProject.getFile(testFileWithContent.getName());
+		assertFalse("Nested file should not have been copied to new location", copiedFile.exists());
+		copiedFile = testFolder.getFile(testFileWithContent.getName());
+		assertTrue("Nested file should have been copied to existing parent", copiedFile.exists());
+		assertTrue("Source file was altered", snapFile.isValid(testFolder));
+
+		undo();
+		assertFalse("Copy folder undo failed", copiedFolder.exists());
+		assertTrue("Source file was altered during undo", snapFile
+				.isValid(testFolder));
+		assertTrue("Source folder was altered during undo", snapFolder
+				.isValid(testProject));
+
+		redo();
+		assertTrue("Source folder was altered during redo", snapFolder
+				.isValid(testProject));
+		assertTrue("Folder copy does not match on redo", snapFolder
+				.isValid(targetProject));
+		assertTrue("Source file was altered during redo", snapFile
+				.isValid(testFolder));
+	}
+
 	public void testFileAndFolderCopySameDests() throws ExecutionException,
 			CoreException {
 		// copying a file and folder, keeping same name to a new project
@@ -1953,19 +2074,19 @@
 		// The operation should know that undoing is dangerous
 		undoExpectFail(op);
 	}
-	
+
 	public void test162655() throws ExecutionException, CoreException {
 		DeleteResourcesOperation op = new DeleteResourcesOperation(
 				new IResource[] { testProject }, "testProjectDelete", false);
 		execute(op);
 		assertFalse("Project delete failed", testProject.exists());
-		
+
 		// recreate outside the scope of undo
 		testProject = getWorkspace().getRoot().getProject(TEST_PROJECT_NAME);
 		testProject.create(getMonitor());
 		testProject.open(getMonitor());
 		assertTrue("Project creation failed", testProject.exists());
-	
+
 		// Now that project exists again, the undo should fail.
 		undoExpectFail(op);
 	}