blob: 63541b680b61c6b51386e4888c6198505fc12307 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 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.actions;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
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.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.StatusUtil;
import org.eclipse.ui.internal.ide.dialogs.IDEResourceInfoUtils;
import org.eclipse.ui.wizards.datatransfer.FileStoreStructureProvider;
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 messageShell;
/**
* 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 = NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_copyNameTwoArgs,
new Integer(counter), resourceName);
else
nameSegment = NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_copyNameOneArg,
resourceName);
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) {
messageShell = 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 NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_deepCopyQuestion,
source.getFullPath().makeRelative());
}
/**
* Checks whether the infos exist.
*
* @param stores
* the file infos to test
* @return Multi status with one error message for each missing file.
*/
IStatus checkExist(IFileStore[] stores) {
MultiStatus multiStatus = new MultiStatus(PlatformUI.PLUGIN_ID,
IStatus.OK, getProblemsMessage(), null);
for (int i = 0; i < stores.length; i++) {
if (stores[i].fetchInfo().exists() == false) {
String message = NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_resourceDeleted,
stores[i].getName());
IStatus status = new Status(IStatus.ERROR,
PlatformUI.PLUGIN_ID, IStatus.OK, message, null);
multiStatus.add(status);
}
}
return multiStatus;
}
/**
* Checks whether the resources with the given names exist.
*
* @param resources
* IResources to checl
* @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) {
URI location = resource.getLocationURI();
String message = null;
if (location != null) {
IFileInfo info = IDEResourceInfoUtils.getFileInfo(location);
if (info == null || info.exists() == false) {
if (resource.isLinked()) {
message = NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_missingLinkTarget,
resource.getName());
} else {
message = NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_resourceDeleted,
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 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 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 = NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteMergeQuestion,
destination.getFullPath()
.makeRelative());
} else {
if (destination.isLinked()) {
message = NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteNoMergeLinkQuestion,
destination.getFullPath()
.makeRelative());
} else {
message = NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteNoMergeNoLinkQuestion,
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 {
String[] bindings = new String[] {
IDEResourceInfoUtils.getLocationText(destination),
IDEResourceInfoUtils
.getDateStringValue(destination),
IDEResourceInfoUtils.getLocationText(source),
IDEResourceInfoUtils.getDateStringValue(source) };
message = NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteWithDetailsQuestion,
bindings);
}
MessageDialog dialog = new MessageDialog(
messageShell,
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_resourceExists,
null, message, MessageDialog.QUESTION, labels, 0);
dialog.open();
if (dialog.getReturnCode() == SWT.DEFAULT) {
// A window close returns SWT.DEFAULT, which has to be
// mapped to a cancel
result[0] = IDialogConstants.CANCEL_ID;
} else {
result[0] = resultId[dialog.getReturnCode()];
}
}
};
messageShell.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 {
subMonitor
.beginTask(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_CopyResourcesTask,
resources.length);
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, new SubProgressMonitor(
subMonitor, 1));
} 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, 1));
}
} else {
if (existing != null) {
if (homogenousResources(source, existing))
copyExisting(source, existing, new SubProgressMonitor(
subMonitor, 1));
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, 1));
}
} else {
source.copy(destinationPath, IResource.SHALLOW,
new SubProgressMonitor(subMonitor, 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. The current Thread is
* halted while the resources are copied using a WorkspaceModifyOperation.
* This method should be called from the UIThread.
*
* @param resources
* the resources to copy
* @param destination
* destination to which resources will be copied
* @return IResource[] the resulting {@link IResource}[]
* @see WorkspaceModifyOperation
* @see Display#getThread()
* @see Thread#currentThread()
*/
public IResource[] copyResources(final IResource[] resources,
IContainer destination) {
return copyResources(resources, destination, true, null);
}
/**
* Copies the given resources to the destination in the current Thread
* without forking a new Thread or blocking using a
* WorkspaceModifyOperation. It recommended that this method only be called
* from a {@link WorkspaceJob} to avoid possible deadlock.
*
* @param resources
* the resources to copy
* @param destination
* destination to which resources will be copied
* @param monitor
* the monitor that information will be sent to.
* @return IResource[] the resulting {@link IResource}[]
* @see WorkspaceModifyOperation
* @see WorkspaceJob
* @since 3.2
*/
public IResource[] copyResourcesInCurrentThread(final IResource[] resources,
IContainer destination, IProgressMonitor monitor) {
return copyResources(resources, destination, false, monitor);
}
/**
* Copies the given resources to the destination.
*
* @param resources
* the resources to copy
* @param destination
* destination to which resources will be copied
* @return IResource[] the resulting {@link IResource}[]
*/
private IResource[] copyResources(final IResource[] resources,
IContainer destination, boolean fork, IProgressMonitor monitor) {
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) {
displayError(resourceStatus);
return copiedResources[0];
}
String errorMsg = validateDestination(destination, resources);
if (errorMsg != null) {
displayError(errorMsg);
return copiedResources[0];
}
if (fork) {
WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
public void execute(IProgressMonitor monitor) {
copyResources(resources, destinationPath, copiedResources,
monitor);
}
};
try {
PlatformUI.getWorkbench().getProgressService().run(true, true,
op);
} catch (InterruptedException e) {
return copiedResources[0];
} catch (InvocationTargetException e) {
display(e);
}
}
else
copyResources(resources, destinationPath, copiedResources, monitor);
// If errors occurred, open an Error dialog
if (errorStatus != null) {
displayError(errorStatus);
errorStatus = null;
}
return copiedResources[0];
}
private void display(InvocationTargetException e) {
// CoreExceptions are collected above, but unexpected runtime
// exceptions and errors may still occur.
IDEWorkbenchPlugin.getDefault().getLog().log(
StatusUtil.newStatus(IStatus.ERROR, MessageFormat.format(
"Exception in {0}.performCopy(): {1}", //$NON-NLS-1$
new Object[] { getClass().getName(),
e.getTargetException() }), null));
displayError(NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_internalError,
e.getTargetException().getMessage()));
}
/**
* Copies the given URIS and folders to the destination. The current Thread
* is halted while the resources are copied using a
* WorkspaceModifyOperation. This method should be called from the UI
* Thread.
*
* @param uris
* the URIs to copy
* @param destination
* destination to which files will be copied
* @see WorkspaceModifyOperation
* @see Display#getThread()
* @see Thread#currentThread()
* @since 3.2
*/
public void copyFiles(URI[] uris, IContainer destination) {
IFileStore[] stores = buildFileStores(uris);
if (stores == null)
return;
copyFileStores(destination, stores, true, null);
}
/**
* Copies the given files and folders to the destination without forking a
* new Thread or blocking using a WorkspaceModifyOperation. It is
* recommended that this method only be called from a {@link WorkspaceJob}
* to avoid possible deadlock.
*
* @param uris
* the URIs to copy
* @param destination
* destination to which URIS will be copied
* @param monitor
* the monitor that information will be sent to.
* @see WorkspaceModifyOperation
* @see WorkspaceJob
* @since 3.2
*/
public void copyFilesInCurrentThread(URI[] uris, IContainer destination,
IProgressMonitor monitor) {
IFileStore[] stores = buildFileStores(uris);
if (stores == null)
return;
copyFileStores(destination, stores, false, monitor);
}
/**
* Build the collection of fileStores that map to fileNames. If any of them
* cannot be found then match then return <code>null</code>.
*
* @param uris
* @return IFileStore[]
*/
private IFileStore[] buildFileStores(URI[] uris) {
IFileStore[] stores = new IFileStore[uris.length];
for (int i = 0; i < uris.length; i++) {
IFileStore store;
try {
store = EFS.getStore(uris[i]);
} catch (CoreException e) {
IDEWorkbenchPlugin.log(e.getMessage(), e);
reportFileInfoNotFound(uris[i].toString());
return null;
}
if (store == null) {
reportFileInfoNotFound(uris[i].toString());
return null;
}
stores[i] = store;
}
return stores;
}
/**
* Copies the given files and folders to the destination. The current Thread
* is halted while the resources are copied using a
* WorkspaceModifyOperation. This method should be called from the UI
* Thread.
*
* @param fileNames
* names of the files to copy
* @param destination
* destination to which files will be copied
* @see WorkspaceModifyOperation
* @see Display#getThread()
* @see Thread#currentThread()
* @since 3.2
*/
public void copyFiles(final String[] fileNames, IContainer destination) {
IFileStore[] stores = buildFileStores(fileNames);
if (stores == null)
return;
copyFileStores(destination, stores, true, null);
}
/**
* Copies the given files and folders to the destination without forking a
* new Thread or blocking using a WorkspaceModifyOperation. It is
* recommended that this method only be called from a {@link WorkspaceJob}
* to avoid possible deadlock.
*
* @param fileNames
* names of the files to copy
* @param destination
* destination to which files will be copied
* @param monitor
* the monitor that information will be sent to.
* @see WorkspaceModifyOperation
* @see WorkspaceJob
* @since 3.2
*/
public void copyFilesInCurrentThread(final String[] fileNames, IContainer destination,
IProgressMonitor monitor) {
IFileStore[] stores = buildFileStores(fileNames);
if (stores == null)
return;
copyFileStores(destination, stores, false, monitor);
}
/**
* Build the collection of fileStores that map to fileNames. If any of them
* cannot be found then match then return null.
*
* @param fileNames
* @return IFileStore[]
*/
private IFileStore[] buildFileStores(final String[] fileNames) {
IFileStore[] stores = new IFileStore[fileNames.length];
for (int i = 0; i < fileNames.length; i++) {
IFileStore store = IDEResourceInfoUtils.getFileStore(fileNames[i]);
if (store == null) {
reportFileInfoNotFound(fileNames[i]);
return null;
}
stores[i] = store;
}
return stores;
}
/**
* Report that a file info could not be found.
*
* @param fileName
*/
private void reportFileInfoNotFound(final String fileName) {
messageShell.getDisplay().syncExec(new Runnable() {
public void run() {
ErrorDialog
.openError(
messageShell,
getProblemsTitle(),
NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_infoNotFound,
fileName), null);
}
});
}
/**
* Copies the given files and folders to the destination.
*
* @param stores
* the file stores to copy
* @param destination
* destination to which files will be copied
*/
private void copyFileStores(IContainer destination,
final IFileStore[] stores, boolean fork, IProgressMonitor monitor) {
// 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(stores);
if (fileStatus.getSeverity() != IStatus.OK) {
displayError(fileStatus);
return;
}
String errorMsg = validateImportDestinationInternal(destination, stores);
if (errorMsg != null) {
displayError(errorMsg);
return;
}
final IPath destinationPath = destination.getFullPath();
if (fork) {
WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
public void execute(IProgressMonitor monitor) {
copyFileStores(stores, destinationPath, monitor);
}
};
try {
PlatformUI.getWorkbench().getProgressService().run(true, true,
op);
} catch (InterruptedException e) {
return;
} catch (InvocationTargetException exception) {
display(exception);
}
} else
copyFileStores(stores, destinationPath, monitor);
// If errors occurred, open an Error dialog
if (errorStatus != null) {
displayError(errorStatus);
errorStatus = null;
}
}
/**
* Display the supplied status in an error dialog.
*
* @param fileStatus
*/
private void displayError(final IStatus status) {
messageShell.getDisplay().syncExec(new Runnable() {
public void run() {
ErrorDialog.openError(messageShell, getProblemsTitle(), null,
status);
}
});
}
/**
* 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) {
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) {
messageShell.getDisplay().syncExec(new Runnable() {
public void run() {
MessageDialog.openError(messageShell, 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;
}
return (IFile) ((IAdaptable) resource).getAdapter(IFile.class);
}
/**
* 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.
* @deprecated This method is not longer in use anywhere in this class and
* is only provided for backwards compatability with subclasses
* of the receiver.
*/
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;
}
return (IFolder) ((IAdaptable) resource).getAdapter(IFolder.class);
}
/**
* 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$
messageShell.getDisplay().syncExec(new Runnable() {
public void run() {
IInputValidator validator = new IInputValidator() {
public String isValid(String string) {
if (resource.getName().equals(string)) {
return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_nameMustBeDifferent;
}
IStatus status = workspace.validateName(string,
resource.getType());
if (!status.isOK()) {
return status.getMessage();
}
if (workspace.getRoot().exists(prefix.append(string))) {
return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_nameExists;
}
return null;
}
};
InputDialog dialog = new InputDialog(
messageShell,
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_inputDialogTitle,
NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_inputDialogMessage,
resource.getName()), 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.CopyFilesAndFoldersOperation_operationTitle;
}
/**
* Returns the message for this operation's problems dialog.
*
* @return the problems message
*/
protected String getProblemsMessage() {
return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_problemMessage;
}
/**
* Returns the title for this operation's problems dialog.
*
* @return the problems dialog title
*/
protected String getProblemsTitle() {
return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_copyFailedTitle;
}
/**
* 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 stores into the provided container.
* Returns a status indicating if the import was successful.
*
* @param stores
* stores 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(IFileStore[] stores, IContainer target,
IProgressMonitor monitor) {
IOverwriteQuery query = new IOverwriteQuery() {
public String queryOverwrite(String pathString) {
if (alwaysOverwrite)
return ALL;
final String returnCode[] = { CANCEL };
final String msg = NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteQuestion,
pathString);
final String[] options = { IDialogConstants.YES_LABEL,
IDialogConstants.YES_TO_ALL_LABEL,
IDialogConstants.NO_LABEL,
IDialogConstants.CANCEL_LABEL };
messageShell.getDisplay().syncExec(new Runnable() {
public void run() {
MessageDialog dialog = new MessageDialog(
messageShell,
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_question,
null, msg, MessageDialog.QUESTION, options, 0);
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(),
stores[0].getParent(), FileStoreStructureProvider.INSTANCE,
query, Arrays.asList(stores));
op.setContext(messageShell);
op.setCreateContainerStructure(false);
try {
op.run(monitor);
} catch (InterruptedException e) {
return;
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof CoreException) {
displayError(((CoreException) e.getTargetException())
.getStatus());
} else {
display(e);
}
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);
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);
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.CopyFilesAndFoldersOperation_destinationAccessError;
}
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.CopyFilesAndFoldersOperation_parentNotEqual;
}
IPath sourceLocation = sourceResource.getLocation();
if (sourceLocation == null) {
if (sourceResource.isLinked()) {
// Don't allow copying linked resources with undefined path
// variables. See bug 28754.
return NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_missingPathVariable,
sourceResource.getName());
}
return NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_resourceDeleted,
sourceResource.getName());
}
if (sourceLocation.equals(destinationLocation)) {
return NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_sameSourceAndDest,
sourceResource.getName());
}
// is the source a parent of the destination?
if (sourceLocation.isPrefixOf(destinationLocation)) {
return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_destinationDescendentError;
}
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 NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_missingPathVariable,
destination.getName());
}
return NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_resourceDeleted,
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, messageShell);
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) {
IFileStore[] stores = new IFileStore[sourceNames.length];
for (int i = 0; i < sourceNames.length; i++) {
IFileStore store = IDEResourceInfoUtils
.getFileStore(sourceNames[i]);
if (store == null)
return NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_infoNotFound,
sourceNames[i]);
stores[i] = store;
}
return validateImportDestinationInternal(destination, stores);
}
/**
* Checks whether the destination is valid for copying the source file
* stores.
* <p>
* Note this method is for internal use only. It is not API.
* </p>
* <p>
* TODO Bug 117804. This method has been renamed to avoid a bug in the
* Eclipse compiler with regards to visibility and type resolution when
* linking.
* </p>
*
* @param destination
* the destination container
* @param sourceStores
* the source IFileStore
* @return an error message, or <code>null</code> if the path is valid
*/
private String validateImportDestinationInternal(IContainer destination,
IFileStore[] sourceStores) {
if (!isAccessible(destination)) {
return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_destinationAccessError;
}
String destinationMessage = validateDestinationLocation(destination);
if (destinationMessage != null) {
return destinationMessage;
}
IFileStore destinationStore;
try {
destinationStore = EFS.getStore(destination.getLocationURI());
} catch (CoreException exception) {
IDEWorkbenchPlugin.log(exception.getLocalizedMessage(), exception);
return NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_internalError,
exception.getLocalizedMessage());
}
for (int i = 0; i < sourceStores.length; i++) {
IFileStore sourceStore = sourceStores[i];
IFileStore sourceParentStore = sourceStore.getParent();
if (sourceStore != null) {
if (destinationStore.equals(sourceStore)
|| (sourceParentStore != null && destinationStore
.equals(sourceParentStore))) {
return NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_importSameSourceAndDest,
sourceStore.getName());
}
// work around bug 16202. replacement for
// sourcePath.isPrefixOf(destinationPath)
IFileStore destinationParent = destinationStore.getParent();
if (sourceStore.isParentOf(destinationParent))
return IDEWorkbenchMessages.CopyFilesAndFoldersOperation_destinationDescendentError;
}
}
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 NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_sameSourceAndDest,
source.getName());
}
}
} catch (CoreException exception) {
displayError(NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_internalError,
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
* @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) {
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)) {
displayError(NLS
.bind(
IDEWorkbenchMessages.CopyFilesAndFoldersOperation_overwriteProblem,
destinationPath, sourcePath));
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(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()]);
}
private void copyResources(final IResource[] resources,
final IPath destinationPath, final IResource[][] copiedResources,
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);
if (copyResources == null) {
if (canceled)
return;
displayError(IDEWorkbenchMessages.CopyFilesAndFoldersOperation_nameCollision);
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;
}
private void copyFileStores(final IFileStore[] stores,
final IPath destinationPath, 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(stores, container, monitor);
}
}
}