blob: 80beaa9bce5d450bb45564d9aa0ea733ab49ed51 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.actions;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.dialogs.*;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ContainerGenerator;
import org.eclipse.ui.dialogs.IOverwriteQuery;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.StatusUtil;
import org.eclipse.ui.internal.progress.ProgressMonitorJobsDialog;
import org.eclipse.ui.wizards.datatransfer.FileSystemStructureProvider;
import org.eclipse.ui.wizards.datatransfer.ImportOperation;
/**
* Perform the copy of file and folder resources from the clipboard
* when paste action is invoked.
* <p>
* This class may be instantiated; it is not intended to be subclassed.
* </p>
*/
public class CopyFilesAndFoldersOperation {
/**
* Status containing the errors detected when running the operation or
* <code>null</code> if no errors detected.
*/
private MultiStatus errorStatus;
/**
* The parent shell used to show any dialogs.
*/
private Shell parentShell;
/**
* Whether or not the copy has been canceled by the user.
*/
private boolean canceled = false;
/**
* Overwrite all flag.
*/
private boolean alwaysOverwrite = false;
/**
* Returns a new name for a copy of the resource at the given path in
* the given workspace. This name is determined automatically.
*
* @param originalName the full path of the resource
* @param workspace the workspace
* @return the new full path for the copy
*/
static IPath getAutoNewNameFor(IPath originalName, IWorkspace workspace) {
int counter = 1;
String resourceName = originalName.lastSegment();
IPath leadupSegment = originalName.removeLastSegments(1);
while (true) {
String nameSegment;
if (counter > 1)
nameSegment = IDEWorkbenchMessages.format("CopyFilesAndFoldersOperation.copyNameTwoArgs", new Object[] { new Integer(counter), resourceName }); //$NON-NLS-1$
else
nameSegment = IDEWorkbenchMessages.format("CopyFilesAndFoldersOperation.copyNameOneArg", new Object[] { resourceName }); //$NON-NLS-1$
IPath pathToTry = leadupSegment.append(nameSegment);
if (!workspace.getRoot().exists(pathToTry))
return pathToTry;
counter++;
}
}
/**
* Creates a new operation initialized with a shell.
*
* @param shell parent shell for error dialogs
*/
public CopyFilesAndFoldersOperation(Shell shell) {
parentShell = shell;
}
/**
* Returns whether this operation is able to perform on-the-fly
* auto-renaming of resources with name collisions.
*
* @return <code>true</code> if auto-rename is supported,
* and <code>false</code> otherwise
*/
protected boolean canPerformAutoRename() {
return true;
}
/**
* Returns the message for querying deep copy/move of a linked
* resource.
*
* @param source resource the query is made for
* @return the deep query message
*/
protected String getDeepCheckQuestion(IResource source) {
return IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.deepCopyQuestion", //$NON-NLS-1$
new Object[] {source.getFullPath().makeRelative()});
}
/**
* Checks whether the files with the given names exist.
*
* @param names path to the file. must not be null.
* If the path is not valid it will not be tested.
* @return Multi status with one error message for each missing file.
*/
IStatus checkExist(String[] names) {
MultiStatus multiStatus = new MultiStatus(
PlatformUI.PLUGIN_ID,
IStatus.OK,
getProblemsMessage(),
null);
for (int i = 0; i < names.length; i++) {
IPath path = new Path(names[i]);
File file = path.toFile();
if (file != null && file.exists() == false) {
String message = IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.resourceDeleted", //$NON-NLS-1$
new Object[] {file.getName()});
IStatus status = new Status(
IStatus.ERROR,
PlatformUI.PLUGIN_ID,
IStatus.OK,
message,
null);
multiStatus.add(status);
}
}
return multiStatus;
}
/**
* Checks whether the files with the given names exist.
*
* @param names path to the file. must not be null.
* If the path is not valid it will not be tested.
* @return Multi status with one error message for each missing file.
*/
IStatus checkExist(IResource[] resources) {
MultiStatus multiStatus = new MultiStatus(
PlatformUI.PLUGIN_ID,
IStatus.OK,
getProblemsMessage(),
null);
for (int i = 0; i < resources.length; i++) {
IResource resource = resources[i];
if (resource != null) {
IPath location = resource.getLocation();
String message = null;
if (location != null) {
File file = location.toFile();
if (file.exists() == false) {
if (resource.isLinked()) {
message = IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.missingLinkTarget", //$NON-NLS-1$
new Object[] {resource.getName()});
} else {
message = IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.resourceDeleted", //$NON-NLS-1$
new Object[] {resource.getName()});
}
}
}
if (message != null) {
IStatus status = new Status(
IStatus.ERROR,
PlatformUI.PLUGIN_ID,
IStatus.OK,
message,
null);
multiStatus.add(status);
}
}
}
return multiStatus;
}
/**
* Check if the user wishes to overwrite the supplied resource or
* all resources.
*
* @param shell the shell to create the overwrite prompt dialog in
* @param source the source resource
* @param destination the resource to be overwritten
* @return one of IDialogConstants.YES_ID, IDialogConstants.YES_TO_ALL_ID,
* IDialogConstants.NO_ID, IDialogConstants.CANCEL_ID indicating whether
* the current resource or all resources can be overwritten, or if the
* operation should be canceled.
*/
private int checkOverwrite(final Shell shell, final IResource source, final IResource destination) {
final int[] result = new int[1];
// Dialogs need to be created and opened in the UI thread
Runnable query = new Runnable() {
public void run() {
String message;
int resultId[] = {
IDialogConstants.YES_ID,
IDialogConstants.YES_TO_ALL_ID,
IDialogConstants.NO_ID,
IDialogConstants.CANCEL_ID};
String labels[] = new String[] {
IDialogConstants.YES_LABEL,
IDialogConstants.YES_TO_ALL_LABEL,
IDialogConstants.NO_LABEL,
IDialogConstants.CANCEL_LABEL};
if (destination.getType() == IResource.FOLDER) {
if (homogenousResources(source, destination)) {
message = IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.overwriteMergeQuestion", //$NON-NLS-1$
new Object[] { destination.getFullPath().makeRelative()});
} else {
if (destination.isLinked()) {
message = IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.overwriteNoMergeLinkQuestion", //$NON-NLS-1$
new Object[] { destination.getFullPath().makeRelative()});
}
else {
message = IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.overwriteNoMergeNoLinkQuestion", //$NON-NLS-1$
new Object[] { destination.getFullPath().makeRelative()});
}
resultId = new int[] {
IDialogConstants.YES_ID,
IDialogConstants.NO_ID,
IDialogConstants.CANCEL_ID};
labels = new String[] {
IDialogConstants.YES_LABEL,
IDialogConstants.NO_LABEL,
IDialogConstants.CANCEL_LABEL};
}
} else {
message = IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.overwriteQuestion", //$NON-NLS-1$
new Object[] { destination.getFullPath().makeRelative()});
}
MessageDialog dialog = new MessageDialog(
shell,
IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.resourceExists"), //$NON-NLS-1$
null,
message,
MessageDialog.QUESTION,
labels,
0);
dialog.open();
result[0] = resultId[dialog.getReturnCode()];
}
};
shell.getDisplay().syncExec(query);
return result[0];
}
/**
* Recursively collects existing files in the specified destination path.
*
* @param destinationPath destination path to check for existing files
* @param copyResources resources that may exist in the destination
* @param existing holds the collected existing files
*/
private void collectExistingReadonlyFiles(IPath destinationPath, IResource[] copyResources, ArrayList existing) {
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
for (int i = 0; i < copyResources.length; i++) {
IResource source = copyResources[i];
IPath newDestinationPath = destinationPath.append(source.getName());
IResource newDestination = workspaceRoot.findMember(newDestinationPath);
IFolder folder;
if (newDestination == null) {
continue;
}
folder = getFolder(newDestination);
if (folder != null) {
IFolder sourceFolder = getFolder(source);
if (sourceFolder != null) {
try {
collectExistingReadonlyFiles(newDestinationPath, sourceFolder.members(), existing);
}
catch (CoreException exception) {
recordError(exception);
}
}
}
else {
IFile file = getFile(newDestination);
if (file != null) {
if (file.isReadOnly()) {
existing.add(file);
}
if (getValidateConflictSource()) {
IFile sourceFile = getFile(source);
if (sourceFile != null) {
existing.add(sourceFile);
}
}
}
}
}
}
/**
* Copies the resources to the given destination. This method is
* called recursively to merge folders during folder copy.
*
* @param resources the resources to copy
* @param destination destination to which resources will be copied
* @param subMonitor a progress monitor for showing progress and for cancelation
*/
protected void copy(IResource[] resources, IPath destination, IProgressMonitor subMonitor) throws CoreException {
for (int i = 0; i < resources.length; i++) {
IResource source = resources[i];
IPath destinationPath = destination.append(source.getName());
IWorkspace workspace = source.getWorkspace();
IWorkspaceRoot workspaceRoot = workspace.getRoot();
IResource existing = workspaceRoot.findMember(destinationPath);
if (source.getType() == IResource.FOLDER && existing != null) {
// the resource is a folder and it exists in the destination, copy the
// children of the folder.
if (homogenousResources(source, existing)) {
IResource[] children = ((IContainer) source).members();
copy(children, destinationPath, subMonitor);
}
else {
// delete the destination folder, copying a linked folder
// over an unlinked one or vice versa. Fixes bug 28772.
delete(existing, new SubProgressMonitor(subMonitor, 0));
source.copy(destinationPath, IResource.SHALLOW, new SubProgressMonitor(subMonitor, 0));
}
} else {
if (existing != null) {
if (homogenousResources(source, existing))
copyExisting(source, existing, subMonitor);
else {
// Copying a linked resource over unlinked or vice versa.
// Can't use setContents here. Fixes bug 28772.
delete(existing, new SubProgressMonitor(subMonitor, 0));
source.copy(destinationPath, IResource.SHALLOW, new SubProgressMonitor(subMonitor, 0));
}
} else {
source.copy(destinationPath, IResource.SHALLOW, new SubProgressMonitor(subMonitor, 0));
}
subMonitor.worked(1);
if (subMonitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
}
/**
* Sets the content of the existing file to the source file content.
*
* @param source source file to copy
* @param existing existing file to set the source content in
* @param subMonitor a progress monitor for showing progress and for cancelation
* @throws CoreException setContents failed
*/
private void copyExisting(IResource source, IResource existing, IProgressMonitor subMonitor) throws CoreException {
IFile existingFile = getFile(existing);
if (existingFile != null) {
IFile sourceFile = getFile(source);
if (sourceFile != null) {
existingFile.setContents(sourceFile.getContents(), IResource.KEEP_HISTORY, new SubProgressMonitor(subMonitor, 0));
}
}
}
/**
* Copies the given resources to the destination.
*
* @param resources the resources to copy
* @param destination destination to which resources will be copied
*/
public IResource[] copyResources(final IResource[] resources, IContainer destination) {
final IPath destinationPath = destination.getFullPath();
final IResource[][] copiedResources = new IResource[1][0];
// test resources for existence separate from validate API.
// Validate is performance critical and resource exists
// check is potentially slow. Fixes bugs 16129/28602.
IStatus resourceStatus = checkExist(resources);
if (resourceStatus.getSeverity() != IStatus.OK) {
ErrorDialog.openError(
parentShell,
getProblemsTitle(),
null, // no special message
resourceStatus);
return copiedResources[0];
}
String errorMsg = validateDestination(destination, resources);
if (errorMsg != null) {
displayError(errorMsg);
return copiedResources[0];
}
WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
public void execute(IProgressMonitor monitor) {
IResource[] copyResources = resources;
// Fix for bug 31116. Do not provide a task name when
// creating the task.
monitor.beginTask("", 100); //$NON-NLS-1$
monitor.setTaskName(getOperationTitle());
monitor.worked(10); // show some initial progress
// Checks only required if this is an exisiting container path.
boolean copyWithAutoRename = false;
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
if (root.exists(destinationPath)) {
IContainer container = (IContainer) root.findMember(destinationPath);
// If we're copying to the source container then perform
// auto-renames on all resources to avoid name collisions.
if (isDestinationSameAsSource(copyResources, container) && canPerformAutoRename()) {
copyWithAutoRename = true;
} else {
// If no auto-renaming will be happening, check for
// potential name collisions at the target resource
copyResources = validateNoNameCollisions(container, copyResources, monitor);
if (copyResources == null) {
if (canceled)
return;
displayError(IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.nameCollision")); //$NON-NLS-1$
return;
}
if (validateEdit(container, copyResources) == false)
return;
}
}
errorStatus = null;
if (copyResources.length > 0) {
if (copyWithAutoRename)
performCopyWithAutoRename(copyResources, destinationPath, monitor);
else
performCopy(copyResources, destinationPath, monitor);
}
copiedResources[0] = copyResources;
}
};
try {
new ProgressMonitorJobsDialog(parentShell).run(true, true, op);
} catch (InterruptedException e) {
return copiedResources[0];
} catch (InvocationTargetException e) {
// CoreExceptions are collected above, but unexpected runtime exceptions and errors may still occur.
Platform.getPlugin(PlatformUI.PLUGIN_ID).getLog().log(StatusUtil.newStatus(
IStatus.ERROR,
MessageFormat.format(
"Exception in {0}.performCopy(): {1}", //$NON-NLS-1$
new Object[] {getClass().getName(), e.getTargetException()}),
null));
displayError(IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.internalError", //$NON-NLS-1$
new Object[] { e.getTargetException().getMessage()}));
}
// If errors occurred, open an Error dialog
if (errorStatus != null) {
ErrorDialog.openError(
parentShell,
getProblemsTitle(),
null, // no special message
errorStatus);
errorStatus = null;
}
return copiedResources[0];
}
/**
* Copies the given files and folders to the destination.
*
* @param fileNames names of the files to copy
* @param destination destination to which files will be copied
*/
public void copyFiles(final String[] fileNames, IContainer destination) {
alwaysOverwrite = false;
// test files for existence separate from validate API
// because an external file may not exist until the copy actually
// takes place (e.g., WinZip contents).
IStatus fileStatus = checkExist(fileNames);
if (fileStatus.getSeverity() != IStatus.OK) {
ErrorDialog.openError(
parentShell,
getProblemsTitle(),
null, // no special message
fileStatus);
return;
}
String errorMsg = validateImportDestination(destination, fileNames);
if (errorMsg != null) {
displayError(errorMsg);
return;
}
final IPath destinationPath = destination.getFullPath();
WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
public void execute(IProgressMonitor monitor) {
// Checks only required if this is an exisiting container path.
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
if (root.exists(destinationPath)) {
IContainer container = (IContainer) root.findMember(destinationPath);
performFileImport(getFiles(fileNames), container, monitor);
}
}
};
try {
new ProgressMonitorJobsDialog(parentShell).run(true, true, op);
} catch (InterruptedException e) {
return;
} catch (InvocationTargetException e) {
// CoreExceptions are collected above, but unexpected runtime exceptions and errors may still occur.
Platform.getPlugin(PlatformUI.PLUGIN_ID).getLog().log(StatusUtil.newStatus(IStatus.ERROR, MessageFormat.format("Exception in {0}.performCopy(): {1}", //$NON-NLS-1$
new Object[] { getClass().getName(), e.getTargetException()}), null));
displayError(IDEWorkbenchMessages.format("CopyFilesAndFoldersOperation.internalError", new Object[] { e.getTargetException().getMessage()})); //$NON-NLS-1$
}
// If errors occurred, open an Error dialog
if (errorStatus != null) {
ErrorDialog.openError(parentShell, getProblemsTitle(), //$NON-NLS-1$
null, // no special message
errorStatus);
errorStatus = null;
}
}
/**
* Creates a file or folder handle for the source resource as if
* it were to be created in the destination container.
*
* @param destination destination container
* @param source source resource
* @return IResource file or folder handle, depending on the source
* type.
*/
IResource createLinkedResourceHandle(IContainer destination, IResource source) {
IWorkspace workspace = destination.getWorkspace();
IWorkspaceRoot workspaceRoot = workspace.getRoot();
IPath linkPath = destination.getFullPath().append(source.getName());
IResource linkHandle;
if (source.getType() == IResource.FOLDER) {
linkHandle = workspaceRoot.getFolder(linkPath);
}
else {
linkHandle = workspaceRoot.getFile(linkPath);
}
return linkHandle;
}
/**
* Removes the given resource from the workspace.
*
* @param resource resource to remove from the workspace
* @param monitor a progress monitor for showing progress and for cancelation
* @return
* true the resource was deleted successfully
* false the resource was not deleted because a CoreException occurred
*/
boolean delete(IResource resource, IProgressMonitor monitor) throws CoreException {
boolean force = false; // don't force deletion of out-of-sync resources
if (resource.getType() == IResource.PROJECT) {
// if it's a project, ask whether content should be deleted too
IProject project = (IProject) resource;
try {
project.delete(true, force, monitor);
} catch (CoreException e) {
recordError(e); // log error
return false;
}
} else {
// if it's not a project, just delete it
int flags = IResource.KEEP_HISTORY;
if (force) {
flags = flags | IResource.FORCE;
}
try {
resource.delete(flags, monitor);
} catch (CoreException e) {
recordError(e); // log error
return false;
}
}
return true;
}
/**
* Opens an error dialog to display the given message.
*
* @param message the error message to show
*/
private void displayError(final String message) {
parentShell.getDisplay().syncExec(new Runnable() {
public void run() {
MessageDialog.openError(parentShell, getProblemsTitle(), message);
}
});
}
/**
* Returns the resource either casted to or adapted to an IFile.
*
* @param resource resource to cast/adapt
* @return the resource either casted to or adapted to an IFile.
* <code>null</code> if the resource does not adapt to IFile
*/
protected IFile getFile(IResource resource) {
if (resource instanceof IFile) {
return (IFile) resource;
}
if (resource instanceof IAdaptable) {
return (IFile) ((IAdaptable) resource).getAdapter(IFile.class);
}
return null;
}
/**
* Returns java.io.File objects for the given file names.
*
* @param fileNames files to return File object for.
* @return java.io.File objects for the given file names.
*/
protected File[] getFiles(String[] fileNames) {
File[] files = new File[fileNames.length];
for (int i = 0; i < fileNames.length; i++) {
files[i] = new File(fileNames[i]);
}
return files;
}
/**
* Returns the resource either casted to or adapted to an IFolder.
*
* @param resource resource to cast/adapt
* @return the resource either casted to or adapted to an IFolder.
* <code>null</code> if the resource does not adapt to IFolder
*/
protected IFolder getFolder(IResource resource) {
if (resource instanceof IFolder) {
return (IFolder) resource;
}
if (resource instanceof IAdaptable) {
return (IFolder) ((IAdaptable) resource).getAdapter(IFolder.class);
}
return null;
}
/**
* Returns a new name for a copy of the resource at the given path in the
* given workspace.
*
* @param originalName the full path of the resource
* @param workspace the workspace
* @return the new full path for the copy, or <code>null</code> if the
* resource should not be copied
*/
private IPath getNewNameFor(final IPath originalName, final IWorkspace workspace) {
final IResource resource = workspace.getRoot().findMember(originalName);
final IPath prefix = resource.getFullPath().removeLastSegments(1);
final String returnValue[] = { "" }; //$NON-NLS-1$
parentShell.getDisplay().syncExec(new Runnable() {
public void run() {
IInputValidator validator = new IInputValidator() {
public String isValid(String string) {
if (resource.getName().equals(string)) {
return IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.nameMustBeDifferent"); //$NON-NLS-1$
}
IStatus status = workspace.validateName(string, resource.getType());
if (!status.isOK()) {
return status.getMessage();
}
if (workspace.getRoot().exists(prefix.append(string))) {
return IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.nameExists"); //$NON-NLS-1$
}
return null;
}
};
InputDialog dialog = new InputDialog(parentShell, IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.inputDialogTitle"), //$NON-NLS-1$
IDEWorkbenchMessages.format("CopyFilesAndFoldersOperation.inputDialogMessage", new String[] { resource.getName()}), //$NON-NLS-1$
getAutoNewNameFor(originalName, workspace).lastSegment().toString(), validator);
dialog.setBlockOnOpen(true);
dialog.open();
if (dialog.getReturnCode() == Window.CANCEL) {
returnValue[0] = null;
} else {
returnValue[0] = dialog.getValue();
}
}
});
if (returnValue[0] == null) {
throw new OperationCanceledException();
}
return prefix.append(returnValue[0]);
}
/**
* Returns the task title for this operation's progress dialog.
*
* @return the task title
*/
protected String getOperationTitle() {
return IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.operationTitle"); //$NON-NLS-1$
}
/**
* Returns the message for this operation's problems dialog.
*
* @return the problems message
*/
protected String getProblemsMessage() {
return IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.problemMessage"); //$NON-NLS-1$
}
/**
* Returns the title for this operation's problems dialog.
*
* @return the problems dialog title
*/
protected String getProblemsTitle() {
return IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.copyFailedTitle"); //$NON-NLS-1$
}
/**
* Returns whether the source file in a destination collision
* will be validateEdited together with the collision itself.
* Returns false. Should return true if the source file is to be
* deleted after the operation.
*
* @return boolean <code>true</code> if the source file in a
* destination collision should be validateEdited.
* <code>false</code> if only the destination should be validated.
*/
protected boolean getValidateConflictSource() {
return false;
}
/**
* Returns whether the given resources are either both linked
* or both unlinked.
*
* @param source source resource
* @param destination destination resource
* @return boolean <code>true</code> if both resources are either
* linked or unlinked. <code>false</code> otherwise.
*/
protected boolean homogenousResources(IResource source, IResource destination) {
boolean isSourceLinked = source.isLinked();
boolean isDestinationLinked = destination.isLinked();
return (isSourceLinked && isDestinationLinked ||
isSourceLinked == false && isDestinationLinked == false);
}
/**
* Returns whether the given resource is accessible.
* Files and folders are always considered accessible and a project is
* accessible if it is open.
*
* @param resource the resource
* @return <code>true</code> if the resource is accessible, and
* <code>false</code> if it is not
*/
private boolean isAccessible(IResource resource) {
switch (resource.getType()) {
case IResource.FILE :
return true;
case IResource.FOLDER :
return true;
case IResource.PROJECT :
return ((IProject) resource).isOpen();
default :
return false;
}
}
/**
* Returns whether any of the given source resources are being
* recopied to their current container.
*
* @param sourceResources the source resources
* @param destination the destination container
* @return <code>true</code> if at least one of the given source
* resource's parent container is the same as the destination
*/
boolean isDestinationSameAsSource(IResource[] sourceResources, IContainer destination) {
IPath destinationLocation = destination.getLocation();
for (int i = 0; i < sourceResources.length; i++) {
IResource sourceResource = sourceResources[i];
if (sourceResource.getParent().equals(destination)) {
return true;
} else if (destinationLocation != null) {
// do thorough check to catch linked resources. Fixes bug 29913.
IPath sourceLocation = sourceResource.getLocation();
IPath destinationResource = destinationLocation.append(sourceResource.getName());
if (sourceLocation != null && sourceLocation.isPrefixOf(destinationResource)) {
return true;
}
}
}
return false;
}
/**
* Copies the given resources to the destination container with
* the given name.
* <p>
* Note: the destination container may need to be created prior to
* copying the resources.
* </p>
*
* @param resources the resources to copy
* @param destination the path of the destination container
* @param monitor a progress monitor for showing progress and for cancelation
* @return <code>true</code> if the copy operation completed without
* errors
*/
private boolean performCopy(IResource[] resources, IPath destination, IProgressMonitor monitor) {
try {
ContainerGenerator generator = new ContainerGenerator(destination);
generator.generateContainer(new SubProgressMonitor(monitor, 10));
IProgressMonitor subMonitor = new SubProgressMonitor(monitor, 75);
copy(resources, destination, subMonitor);
} catch (CoreException e) {
recordError(e); // log error
return false;
} finally {
monitor.done();
}
return true;
}
/**
* Individually copies the given resources to the specified destination
* container checking for name collisions. If a collision is detected,
* it is saved with a new name.
* <p>
* Note: the destination container may need to be created prior to
* copying the resources.
* </p>
*
* @param resources the resources to copy
* @param destination the path of the destination container
* @return <code>true</code> if the copy operation completed without errors.
*/
private boolean performCopyWithAutoRename(IResource[] resources, IPath destination, IProgressMonitor monitor) {
IWorkspace workspace = resources[0].getWorkspace();
try {
ContainerGenerator generator = new ContainerGenerator(destination);
generator.generateContainer(new SubProgressMonitor(monitor, 10));
IProgressMonitor subMonitor = new SubProgressMonitor(monitor, 75);
subMonitor.beginTask(getOperationTitle(), resources.length);
for (int i = 0; i < resources.length; i++) {
IResource source = resources[i];
IPath destinationPath = destination.append(source.getName());
if (workspace.getRoot().exists(destinationPath)) {
destinationPath = getNewNameFor(destinationPath, workspace);
}
if (destinationPath != null) {
try {
source.copy(destinationPath, IResource.SHALLOW, new SubProgressMonitor(subMonitor, 0));
} catch (CoreException e) {
recordError(e); // log error
return false;
}
}
subMonitor.worked(1);
if (subMonitor.isCanceled()) {
throw new OperationCanceledException();
}
}
} catch (CoreException e) {
recordError(e); // log error
return false;
} finally {
monitor.done();
}
return true;
}
/**
* Performs an import of the given files into the provided container.
* Returns a status indicating if the import was successful.
*
* @param files files that are to be imported
* @param target container to which the import will be done
* @param monitor a progress monitor for showing progress and for cancelation
*/
private void performFileImport(File[] files, IContainer target, IProgressMonitor monitor) {
IOverwriteQuery query = new IOverwriteQuery() {
public String queryOverwrite(String pathString) {
if (alwaysOverwrite)
return ALL;
final String returnCode[] = { CANCEL };
final String msg = IDEWorkbenchMessages.format("CopyFilesAndFoldersOperation.overwriteQuestion", new Object[] { pathString }); //$NON-NLS-1$
final String[] options =
{
IDialogConstants.YES_LABEL,
IDialogConstants.YES_TO_ALL_LABEL,
IDialogConstants.NO_LABEL,
IDialogConstants.CANCEL_LABEL };
parentShell.getDisplay().syncExec(new Runnable() {
public void run() {
MessageDialog dialog = new MessageDialog(parentShell, IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.question"), null, msg, MessageDialog.QUESTION, options, 0); //$NON-NLS-1$
dialog.open();
int returnVal = dialog.getReturnCode();
String[] returnCodes = { YES, ALL, NO, CANCEL };
returnCode[0] = returnVal == -1 ? CANCEL : returnCodes[returnVal];
}
});
if (returnCode[0] == ALL) {
alwaysOverwrite = true;
} else if (returnCode[0] == CANCEL) {
canceled = true;
}
return returnCode[0];
}
};
ImportOperation op =
new ImportOperation(
target.getFullPath(),
null,
FileSystemStructureProvider.INSTANCE,
query,
Arrays.asList(files));
op.setContext(parentShell);
op.setCreateContainerStructure(false);
try {
op.run(monitor);
} catch (InterruptedException e) {
return;
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof CoreException) {
final IStatus status = ((CoreException) e.getTargetException()).getStatus();
parentShell.getDisplay().syncExec(new Runnable() {
public void run() {
ErrorDialog.openError(parentShell, IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.importErrorDialogTitle"), //$NON-NLS-1$
null, // no special message
status);
}
});
} else {
// CoreExceptions are handled above, but unexpected runtime exceptions and errors may still occur.
Platform.getPlugin(PlatformUI.PLUGIN_ID).getLog().log(StatusUtil.newStatus(IStatus.ERROR, MessageFormat.format("Exception in {0}.performFileImport(): {1}", //$NON-NLS-1$
new Object[] { getClass().getName(), e.getTargetException()}), null));
displayError(IDEWorkbenchMessages.format("CopyFilesAndFoldersOperation.internalError", //$NON-NLS-1$
new Object[] { e.getTargetException().getMessage()}));
}
return;
}
// Special case since ImportOperation doesn't throw a CoreException on
// failure.
IStatus status = op.getStatus();
if (!status.isOK()) {
if (errorStatus == null)
errorStatus = new MultiStatus(PlatformUI.PLUGIN_ID, IStatus.ERROR, getProblemsMessage(), null); //$NON-NLS-1$
errorStatus.merge(status);
}
}
/**
* Records the core exception to be displayed to the user
* once the action is finished.
*
* @param error a <code>CoreException</code>
*/
private void recordError(CoreException error) {
if (errorStatus == null)
errorStatus = new MultiStatus(PlatformUI.PLUGIN_ID, IStatus.ERROR, getProblemsMessage(), error); //$NON-NLS-1$
errorStatus.merge(error.getStatus());
}
/**
* Checks whether the destination is valid for copying the source
* resources.
* <p>
* Note this method is for internal use only. It is not API.
* </p>
*
* @param destination the destination container
* @param sourceResources the source resources
* @return an error message, or <code>null</code> if the path is valid
*/
public String validateDestination(IContainer destination, IResource[] sourceResources) {
if (!isAccessible(destination)) {
return IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.destinationAccessError"); //$NON-NLS-1$
}
String destinationMessage = validateDestinationLocation(destination);
if (destinationMessage != null) {
return destinationMessage;
}
IContainer firstParent = null;
IPath destinationLocation = destination.getLocation();
for (int i = 0; i < sourceResources.length; i++) {
IResource sourceResource = sourceResources[i];
if (firstParent == null) {
firstParent = sourceResource.getParent();
} else if (firstParent.equals(sourceResource.getParent()) == false) {
// Resources must have common parent. Fixes bug 33398.
return IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.parentNotEqual"); //$NON-NLS-1$
}
IPath sourceLocation = sourceResource.getLocation();
if (sourceLocation == null) {
if (sourceResource.isLinked()) {
// Don't allow copying linked resources with undefined path
// variables. See bug 28754.
return IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.missingPathVariable", //$NON-NLS-1$
new Object[] {sourceResource.getName()});
} else {
return IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.resourceDeleted", //$NON-NLS-1$
new Object[] {sourceResource.getName()});
}
} else {
if (sourceLocation.equals(destinationLocation)) {
return IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.sameSourceAndDest", //$NON-NLS-1$
new Object[] {sourceResource.getName()});
}
// is the source a parent of the destination?
if (sourceLocation.isPrefixOf(destinationLocation)) {
return IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.destinationDescendentError"); //$NON-NLS-1$
}
}
String linkedResourceMessage = validateLinkedResource(destination, sourceResource);
if (linkedResourceMessage != null) {
return linkedResourceMessage;
}
}
return null;
}
/**
* Validates whether the destination location exists.
* Linked resources created on undefined path variables have
* an undefined location.
*
* @param destination destination container
* @return error message or null if destination location is
* valid (non-<code>null</code>)
*/
private String validateDestinationLocation(IContainer destination) {
IPath destinationLocation = destination.getLocation();
if (destinationLocation == null) {
if (destination.isLinked()) {
return IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.missingPathVariable", //$NON-NLS-1$
new Object[] {destination.getName()});
}
else {
return IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.resourceDeleted", //$NON-NLS-1$
new Object[] {destination.getName()});
}
}
return null;
}
/**
* Validates that the given source resources can be copied to the
* destination as decided by the VCM provider.
*
* @param destination copy destination
* @param sourceResources source resources
* @return <code>true</code> all files passed validation or there
* were no files to validate. <code>false</code> one or more files
* did not pass validation.
*/
private boolean validateEdit(IContainer destination, IResource[] sourceResources) {
ArrayList copyFiles = new ArrayList();
collectExistingReadonlyFiles(destination.getFullPath(), sourceResources, copyFiles);
if (copyFiles.size() > 0) {
IFile[] files = (IFile[]) copyFiles.toArray(new IFile[copyFiles.size()]);
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IStatus status = workspace.validateEdit(files, parentShell);
canceled = status.isOK() == false;
return status.isOK();
}
return true;
}
/**
* Checks whether the destination is valid for copying the source
* files.
* <p>
* Note this method is for internal use only. It is not API.
* </p>
*
* @param destination the destination container
* @param sourceNames the source file names
* @return an error message, or <code>null</code> if the path is valid
*/
public String validateImportDestination(IContainer destination, String[] sourceNames) {
if (!isAccessible(destination)) {
return IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.destinationAccessError"); //$NON-NLS-1$
}
String destinationMessage = validateDestinationLocation(destination);
if (destinationMessage != null) {
return destinationMessage;
}
// work around bug 16202. revert when fixed.
IPath destinationPath = destination.getLocation();
File destinationFile = destinationPath.toFile();
for (int i = 0; i < sourceNames.length; i++) {
IPath sourcePath = new Path(sourceNames[i]);
File sourceFile = sourcePath.toFile();
File sourceParentFile = sourcePath.removeLastSegments(1).toFile();
if (sourceFile != null) {
if (destinationFile.compareTo(sourceFile) == 0 ||
(sourceParentFile != null && destinationFile.compareTo(sourceParentFile) == 0)) {
return IDEWorkbenchMessages.format("CopyFilesAndFoldersOperation.importSameSourceAndDest", //$NON-NLS-1$
new Object[] {sourceFile.getName()});
}
// work around bug 16202. replacement for sourcePath.isPrefixOf(destinationPath)
IPath destinationParent = destinationPath.removeLastSegments(1);
while (destinationParent.isEmpty() == false && destinationParent.isRoot() == false) {
destinationFile = destinationParent.toFile();
if (sourceFile.compareTo(destinationFile) == 0) {
return IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.destinationDescendentError"); //$NON-NLS-1$
}
destinationParent = destinationParent.removeLastSegments(1);
}
}
}
return null;
}
/**
* Check if the destination is valid for the given source resource.
*
* @param destination destination container of the operation
* @param source source resource
* @return String error message or null if the destination is valid
*/
private String validateLinkedResource(IContainer destination, IResource source) {
if (source.isLinked() == false) {
return null;
}
IWorkspace workspace = destination.getWorkspace();
IResource linkHandle = createLinkedResourceHandle(destination, source);
IStatus locationStatus = workspace.validateLinkLocation(linkHandle, source.getRawLocation());
if (locationStatus.getSeverity() == IStatus.ERROR) {
return locationStatus.getMessage();
}
IPath sourceLocation = source.getLocation();
if (source.getProject().equals(destination.getProject()) == false &&
source.getType() == IResource.FOLDER &&
sourceLocation != null) {
// prevent merging linked folders that point to the same
// file system folder
try {
IResource[] members = destination.members();
for (int j = 0; j < members.length; j++) {
if (sourceLocation.equals(members[j].getLocation()) &&
source.getName().equals(members[j].getName())) {
return IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.sameSourceAndDest", //$NON-NLS-1$
new Object[] {source.getName()});
}
}
}
catch (CoreException exception) {
displayError(IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.internalError", //$NON-NLS-1$
new Object[] {exception.getMessage()}));
}
}
return null;
}
/**
* Returns whether moving all of the given source resources to the given
* destination container could be done without causing name collisions.
*
* @param destination the destination container
* @param sourceResources the list of resources
* @param monitor a progress monitor for showing progress and for
* cancelation
* @return <code>true</code> if there would be no name collisions, and
* <code>false</code> if there would
*/
private IResource[] validateNoNameCollisions(
IContainer destination,
IResource[] sourceResources,
IProgressMonitor monitor) {
List copyItems = new ArrayList();
IWorkspaceRoot workspaceRoot = destination.getWorkspace().getRoot();
int overwrite = IDialogConstants.NO_ID;
// Check to see if we would be overwriting a parent folder.
// Cancel entire copy operation if we do.
for (int i = 0; i < sourceResources.length; i++) {
final IResource sourceResource = sourceResources[i];
final IPath destinationPath = destination.getFullPath().append(sourceResource.getName());
final IPath sourcePath = sourceResource.getFullPath();
IResource newResource = workspaceRoot.findMember(destinationPath);
if (newResource != null && destinationPath.isPrefixOf(sourcePath)) {
//Run it inside of a runnable to make sure we get to parent off of the shell as we are not
//in the UI thread.
Runnable notice = new Runnable() {
public void run() {
MessageDialog.openError(
parentShell,
IDEWorkbenchMessages.getString("CopyFilesAndFoldersOperation.overwriteProblemTitle"), //$NON-NLS-1$
IDEWorkbenchMessages.format(
"CopyFilesAndFoldersOperation.overwriteProblem", //$NON-NLS-1$
new Object[] {destinationPath, sourcePath}
)
);
}
};
parentShell.getDisplay().syncExec(notice);
canceled = true;
return null;
}
}
// Check for overwrite conflicts
for (int i = 0; i < sourceResources.length; i++) {
final IResource source = sourceResources[i];
final IPath destinationPath = destination.getFullPath().append(source.getName());
IResource newResource = workspaceRoot.findMember(destinationPath);
if (newResource != null) {
if (overwrite != IDialogConstants.YES_TO_ALL_ID ||
(newResource.getType() == IResource.FOLDER && homogenousResources(source, destination) == false)) {
overwrite = checkOverwrite(parentShell, source, newResource);
}
if (overwrite == IDialogConstants.YES_ID || overwrite == IDialogConstants.YES_TO_ALL_ID) {
copyItems.add(source);
} else if (overwrite == IDialogConstants.CANCEL_ID) {
canceled = true;
return null;
}
} else {
copyItems.add(source);
}
}
return (IResource[]) copyItems.toArray(new IResource[copyItems.size()]);
}
}