/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.ui.actions; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; 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.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.Path; import org.eclipse.core.runtime.QualifiedName; 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.dialogs.ProgressMonitorDialog; import org.eclipse.jface.window.Window; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.WorkspaceModifyOperation; import org.eclipse.ui.dialogs.ContainerGenerator; import org.eclipse.ui.dialogs.IOverwriteQuery; import org.eclipse.ui.wizards.datatransfer.FileSystemStructureProvider; import org.eclipse.ui.wizards.datatransfer.ImportOperation; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.workspace.ModelUtil; import org.teiid.designer.ui.UiConstants; /** * 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. (Ha!) * </p> * * @since 8.0 */ public class CopyFilesAndFoldersOperation implements UiConstants { /** * Status containing the errors detected when running the operation or <code>null</code> if no errors detected. */ MultiStatus errorStatus; /** * The parent shell used to show any dialogs. */ Shell parentShell; /** * Whether or not the copy has been canceled by the user. */ boolean canceled = false; /** * Overwrite all flag. */ boolean alwaysOverwrite = false; /* * TODO: * * The following constants were copied from MetabaseConstants. Since the Metabase * code is responsible for the 'teambase' actions (checkin, checkout, getLatest, etc.) it * is reasonable that the repository-related persistent properties of resources * maintained by the teambase code use a metabase class as part of the property key. * It would be nice if this info were made visible through an extension point so that * other parts of the system could access these properties without hardcoding them * or having a dependency to the MetaBase plugin. * */ public static final String VERSION = "version"; //$NON-NLS-1$ public static final String CHECKED_OUT = "checkedOut"; //$NON-NLS-1$ public static final String CHECK_OUT_COMMENT = "checkOutComment"; //$NON-NLS-1$ public static final String TEAMBASE_PLUGIN_CLASS = "org.teiid.designer.team.metabase.MetabasePlugin"; //$NON-NLS-1$ public static final QualifiedName VERSION_DECORATOR_NAME = new QualifiedName(TEAMBASE_PLUGIN_CLASS, VERSION); public static final QualifiedName CHECKOUT_DECORATOR_NAME = new QualifiedName(TEAMBASE_PLUGIN_CLASS, CHECKED_OUT); public static final QualifiedName CHECK_OUT_COMMENT_NAME = new QualifiedName(TEAMBASE_PLUGIN_CLASS, CHECK_OUT_COMMENT); /** * 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 = UiConstants.Util.getString("CopyFilesAndFoldersOperation.copyNameTwoArgs", new Object[] {new Integer(counter), resourceName}); //$NON-NLS-1$ else nameSegment = UiConstants.Util.getString("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 UiConstants.Util.getString("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 = UiConstants.Util.getString("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 = UiConstants.Util.getString("CopyFilesAndFoldersOperation.missingLinkTarget", //$NON-NLS-1$ new Object[] {resource.getName()}); } else { message = UiConstants.Util.getString("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() { @Override 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 = UiConstants.Util.getString("CopyFilesAndFoldersOperation.overwriteMergeQuestion", //$NON-NLS-1$ new Object[] {destination.getFullPath().makeRelative()}); } else { if (destination.isLinked()) { message = UiConstants.Util.getString("CopyFilesAndFoldersOperation.overwriteNoMergeLinkQuestion", //$NON-NLS-1$ new Object[] {destination.getFullPath().makeRelative()}); } else { message = UiConstants.Util.getString("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 = UiConstants.Util.getString("CopyFilesAndFoldersOperation.overwriteQuestion", //$NON-NLS-1$ new Object[] {destination.getFullPath().makeRelative()}); } MessageDialog dialog = new MessageDialog( shell, UiConstants.Util.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 = ModelerCore.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 (ModelUtil.isIResourceReadOnly(file)) { 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)); sourceCopy(source, destinationPath, 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)); sourceCopy(source, destinationPath, new SubProgressMonitor(subMonitor, 0)); } } else { sourceCopy(source, destinationPath, new SubProgressMonitor(subMonitor, 0)); } subMonitor.worked(1); if (subMonitor.isCanceled()) { throw new OperationCanceledException(); } } } } /** * Copies a resource to the given destination, then nullifies some persistent properties that should not carry forward to a * copy. Note: This method is the only new method in this class. * * @param resource the resource to copy * @param destination destination to which resource will be copied * @param subMonitor a progress monitor for showing progress and for cancelation */ public void sourceCopy( IResource source, IPath destinationPath, IProgressMonitor subMonitor ) throws CoreException { source.copy(destinationPath, IResource.SHALLOW, new SubProgressMonitor(subMonitor, 0)); // Now that the copy has been done, find the file and remove some of the properties IWorkspaceRoot workspaceRoot = ModelerCore.getWorkspace().getRoot(); IResource newDestination = workspaceRoot.findMember(destinationPath); newDestination.setPersistentProperty(VERSION_DECORATOR_NAME, null); newDestination.setPersistentProperty(CHECKOUT_DECORATOR_NAME, null); newDestination.setPersistentProperty(CHECK_OUT_COMMENT_NAME, null); } /** * 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() { @Override 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 = ModelerCore.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(UiConstants.Util.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 ProgressMonitorDialog(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. Util.log(IStatus.ERROR, MessageFormat.format("Exception in {0}.performCopy(): {1}", //$NON-NLS-1$ new Object[] {getClass().getName(), e.getTargetException()})); displayError(UiConstants.Util.getString("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() { @Override public void execute( IProgressMonitor monitor ) { // Checks only required if this is an exisiting container path. IWorkspaceRoot root = ModelerCore.getWorkspace().getRoot(); if (root.exists(destinationPath)) { IContainer container = (IContainer)root.findMember(destinationPath); performFileImport(getFiles(fileNames), container, monitor); } } }; try { new ProgressMonitorDialog(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. Util.log(IStatus.ERROR, MessageFormat.format("Exception in {0}.performCopy(): {1}", //$NON-NLS-1$ new Object[] {getClass().getName(), e.getTargetException()})); displayError(UiConstants.Util.getString("CopyFilesAndFoldersOperation.internalError", new Object[] {e.getTargetException().getMessage()})); //$NON-NLS-1$ } // If errors occurred, open an Error dialog if (errorStatus != null) { ErrorDialog.openError(parentShell, getProblemsTitle(), 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 ) { 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 */ void displayError( final String message ) { parentShell.getDisplay().syncExec(new Runnable() { @Override 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; } 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. */ 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$ parentShell.getDisplay().syncExec(new Runnable() { @Override public void run() { IInputValidator validator = new IInputValidator() { @Override public String isValid( String string ) { if (resource.getName().equals(string)) { return UiConstants.Util.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 UiConstants.Util.getString("CopyFilesAndFoldersOperation.nameExists"); //$NON-NLS-1$ } return null; } }; InputDialog dialog = new InputDialog( parentShell, UiConstants.Util.getString("CopyFilesAndFoldersOperation.inputDialogTitle"), //$NON-NLS-1$ UiConstants.Util.getString("CopyFilesAndFoldersOperation.inputDialogMessage", (Object[])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 UiConstants.Util.getString("CopyFilesAndFoldersOperation.operationTitle"); //$NON-NLS-1$ } /** * Returns the message for this operation's problems dialog. * * @return the problems message */ protected String getProblemsMessage() { return UiConstants.Util.getString("CopyFilesAndFoldersOperation.problemMessage"); //$NON-NLS-1$ } /** * Returns the title for this operation's problems dialog. * * @return the problems dialog title */ protected String getProblemsTitle() { return UiConstants.Util.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 */ 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. */ 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)); sourceCopy(source, destinationPath, 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 */ void performFileImport( File[] files, IContainer target, IProgressMonitor monitor ) { IOverwriteQuery query = new IOverwriteQuery() { @Override public String queryOverwrite( String pathString ) { if (alwaysOverwrite) return ALL; final String returnCode[] = {CANCEL}; final String msg = UiConstants.Util.getString("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() { @Override public void run() { MessageDialog dialog = new MessageDialog( parentShell, UiConstants.Util.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() { @Override public void run() { ErrorDialog.openError(parentShell, UiConstants.Util.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. Util.log(IStatus.ERROR, MessageFormat.format("Exception in {0}.performFileImport(): {1}", //$NON-NLS-1$ new Object[] {getClass().getName(), e.getTargetException()})); displayError(UiConstants.Util.getString("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); 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 UiConstants.Util.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 UiConstants.Util.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 UiConstants.Util.getString("CopyFilesAndFoldersOperation.missingPathVariable", //$NON-NLS-1$ new Object[] {sourceResource.getName()}); } return UiConstants.Util.getString("CopyFilesAndFoldersOperation.resourceDeleted", //$NON-NLS-1$ new Object[] {sourceResource.getName()}); } if (sourceLocation.equals(destinationLocation)) { return UiConstants.Util.getString("CopyFilesAndFoldersOperation.sameSourceAndDest", //$NON-NLS-1$ new Object[] {sourceResource.getName()}); } // is the source a parent of the destination? if (sourceLocation.isPrefixOf(destinationLocation)) { return UiConstants.Util.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 UiConstants.Util.getString("CopyFilesAndFoldersOperation.missingPathVariable", //$NON-NLS-1$ new Object[] {destination.getName()}); } return UiConstants.Util.getString("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. */ 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 = ModelerCore.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 UiConstants.Util.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 UiConstants.Util.getString("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 UiConstants.Util.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); IPath location = null; try { location = ModelUtil.getLocation(source); } catch (CoreException ex) { displayError(UiConstants.Util.getString("CopyFilesAndFoldersOperation.internalError", //$NON-NLS-1$ new Object[] {ex.getMessage()})); return null; } // location will never be null if an exception has not been thrown IStatus locationStatus = workspace.validateLinkLocation(linkHandle, location); 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 UiConstants.Util.getString("CopyFilesAndFoldersOperation.sameSourceAndDest", //$NON-NLS-1$ new Object[] {source.getName()}); } } } catch (CoreException exception) { displayError(UiConstants.Util.getString("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 */ 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() { @Override public void run() { MessageDialog.openError(parentShell, UiConstants.Util.getString("CopyFilesAndFoldersOperation.overwriteProblemTitle"), //$NON-NLS-1$ UiConstants.Util.getString("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()]); } }