blob: c32a40f040148a257fb24c8b4667cf4b90de3d55 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2007 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.ide.undo;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.ObjectUndoContext;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
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.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.ide.undo.ContainerDescription;
import org.eclipse.ui.internal.ide.undo.FileDescription;
import org.eclipse.ui.internal.ide.undo.UndoMessages;
/**
* WorkspaceUndoUtil defines common utility methods and constants used by
* clients who create undoable workspace operations.
*
* @since 3.3
*
*/
public class WorkspaceUndoUtil {
private static ObjectUndoContext tasksUndoContext;
private static ObjectUndoContext bookmarksUndoContext;
/**
* Return the undo context that should be used for workspace-wide operations
*
* @return the undo context suitable for workspace-level operations.
*/
public static IUndoContext getWorkspaceUndoContext() {
return WorkbenchPlugin.getDefault().getOperationSupport()
.getUndoContext();
}
/**
* Return the undo context that should be used for operations involving
* tasks.
*
* @return the tasks undo context
*/
public static IUndoContext getTasksUndoContext() {
if (tasksUndoContext == null) {
tasksUndoContext = new ObjectUndoContext(new Object(),
"Tasks Context"); //$NON-NLS-1$
tasksUndoContext.addMatch(getWorkspaceUndoContext());
}
return tasksUndoContext;
}
/**
* Return the undo context that should be used for operations involving
* bookmarks.
*
* @return the bookmarks undo context
*/
public static IUndoContext getBookmarksUndoContext() {
if (bookmarksUndoContext == null) {
bookmarksUndoContext = new ObjectUndoContext(new Object(),
"Bookmarks Context"); //$NON-NLS-1$
bookmarksUndoContext.addMatch(getWorkspaceUndoContext());
}
return bookmarksUndoContext;
}
/**
* Make an <code>IAdaptable</code> that adapts to the specified shell,
* suitable for passing for passing to any
* {@link org.eclipse.core.commands.operations.IUndoableOperation} or
* {@link org.eclipse.core.commands.operations.IOperationHistory} method
* that requires an {@link org.eclipse.core.runtime.IAdaptable}
* <code>uiInfo</code> parameter.
*
* @param shell
* the shell that should be returned by the IAdaptable when asked
* to adapt a shell. If this parameter is <code>null</code>,
* the returned shell will also be <code>null</code>.
*
* @return an IAdaptable that will return the specified shell.
*/
public static IAdaptable getUIInfoAdapter(final Shell shell) {
return new IAdaptable() {
public Object getAdapter(Class clazz) {
if (clazz == Shell.class) {
return shell;
}
return null;
}
};
}
private WorkspaceUndoUtil() {
// should not construct
}
/**
* Delete all of the specified resources, returning resource descriptions
* that can be used to restore them.
*
* @param resourcesToDelete
* an array of resources to be deleted
* @param monitor
* the progress monitor to use to show the operation's progress
* @param uiInfo
* the IAdaptable (or <code>null</code>) provided by the
* caller in order to supply UI information for prompting the
* user if necessary. When this parameter is not
* <code>null</code>, it contains an adapter for the
* org.eclipse.swt.widgets.Shell.class
*
* @param deleteContent
* a boolean indicating whether project content should be deleted
* when a project resource is to be deleted
* @return an array of ResourceDescriptions that can be used to restore the
* deleted resources.
* @throws CoreException
* propagates any CoreExceptions thrown from the resources API
*/
static ResourceDescription[] delete(IResource[] resourcesToDelete,
IProgressMonitor monitor, IAdaptable uiInfo, boolean deleteContent)
throws CoreException {
final List exceptions = new ArrayList();
boolean forceOutOfSyncDelete = false;
ResourceDescription[] returnedResourceDescriptions = new ResourceDescription[resourcesToDelete.length];
monitor.beginTask("", resourcesToDelete.length); //$NON-NLS-1$
monitor
.setTaskName(UndoMessages.AbstractResourcesOperation_DeleteResourcesProgress);
try {
for (int i = 0; i < resourcesToDelete.length; ++i) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
IResource resource = resourcesToDelete[i];
try {
returnedResourceDescriptions[i] = delete(resource,
new SubProgressMonitor(monitor, 1), uiInfo,
forceOutOfSyncDelete, deleteContent);
} catch (CoreException e) {
if (resource.getType() == IResource.FILE) {
IStatus[] children = e.getStatus().getChildren();
if (children.length == 1
&& children[0].getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
int result = queryDeleteOutOfSync(resource, uiInfo);
if (result == IDialogConstants.YES_ID) {
// retry the delete with a force out of sync
delete(resource, new SubProgressMonitor(
monitor, 1), uiInfo, true,
deleteContent);
} else if (result == IDialogConstants.YES_TO_ALL_ID) {
// all future attempts should force out of
// sync
forceOutOfSyncDelete = true;
delete(resource, new SubProgressMonitor(
monitor, 1), uiInfo,
forceOutOfSyncDelete, deleteContent);
} else if (result == IDialogConstants.CANCEL_ID) {
throw new OperationCanceledException();
} else {
exceptions.add(e);
}
} else {
exceptions.add(e);
}
} else {
exceptions.add(e);
}
}
}
IStatus result = createResult(exceptions);
if (!result.isOK()) {
throw new CoreException(result);
}
} finally {
monitor.done();
}
return returnedResourceDescriptions;
}
/**
* Copies the resources to the given destination. This method can be called
* recursively to merge folders during folder copy.
*
* @param resources
* the resources to be copied
* @param destination
* the destination path for the resources, relative to the
* workspace
* @param resourcesAtDestination
* A list used to record the new copies.
* @param monitor
* the progress monitor used to show progress
* @param uiInfo
* the IAdaptable (or <code>null</code>) provided by the
* caller in order to supply UI information for prompting the
* user if necessary. When this parameter is not
* <code>null</code>, it contains an adapter for the
* org.eclipse.swt.widgets.Shell.class
* @param pathIncludesName
* a boolean that indicates whether the specified path includes
* the resource's name at the destination. If this value is
* <code>true</code>, the destination will contain the desired
* name of the resource (usually only desired when only one
* resource is being copied). If this value is <code>false</code>,
* each resource's name will be appended to the destination.
* @return an array of ResourceDescriptions describing any resources that
* were overwritten by the copy operation
* @throws CoreException
* propagates any CoreExceptions thrown from the resources API
*/
static ResourceDescription[] copy(IResource[] resources, IPath destination,
List resourcesAtDestination, IProgressMonitor monitor,
IAdaptable uiInfo, boolean pathIncludesName) throws CoreException {
monitor.beginTask("", resources.length); //$NON-NLS-1$
monitor
.setTaskName(UndoMessages.AbstractResourcesOperation_CopyingResourcesProgress);
List overwrittenResources = new ArrayList();
for (int i = 0; i < resources.length; i++) {
IResource source = resources[i];
IPath destinationPath;
if (pathIncludesName) {
destinationPath = destination;
} else {
destinationPath = destination.append(source.getName());
}
IWorkspaceRoot workspaceRoot = getWorkspaceRoot();
IResource existing = workspaceRoot.findMember(destinationPath);
if (source.getType() == IResource.FOLDER && existing != null) {
// The resource is a folder and it exists in the destination.
// Copy its children to the existing destination.
if (source.isLinked() == existing.isLinked()) {
IResource[] children = ((IContainer) source).members();
ResourceDescription[] overwritten = copy(children,
destinationPath, resourcesAtDestination,
new SubProgressMonitor(monitor, 1), uiInfo, false);
// We don't record the copy since this recursive call will
// do so. Just record the overwrites.
for (int j = 0; j < overwritten.length; j++) {
overwrittenResources.add(overwritten[j]);
}
} else {
// delete the destination folder, copying a linked folder
// over an unlinked one or vice versa. Fixes bug 28772.
ResourceDescription[] deleted = delete(
new IResource[] { existing },
new SubProgressMonitor(monitor, 0), uiInfo, false);
source.copy(destinationPath, IResource.SHALLOW,
new SubProgressMonitor(monitor, 1));
// Record the copy
resourcesAtDestination.add(getWorkspace().getRoot()
.findMember(destinationPath));
for (int j = 0; j < deleted.length; j++) {
overwrittenResources.add(deleted[j]);
}
}
} else {
if (existing != null) {
if (source.isLinked() == existing.isLinked()) {
overwrittenResources.add(copyOverExistingResource(
source, existing, new SubProgressMonitor(
monitor, 1), uiInfo, false));
// Record the "copy"
resourcesAtDestination.add(existing);
} else {
// Copying a linked resource over unlinked or vice
// versa. Can't use setContents here. Fixes bug 28772.
ResourceDescription[] deleted = delete(
new IResource[] { existing },
new SubProgressMonitor(monitor, 0), uiInfo,
false);
source.copy(destinationPath, IResource.SHALLOW,
new SubProgressMonitor(monitor, 1));
// Record the copy
resourcesAtDestination.add(getWorkspace().getRoot()
.findMember(destinationPath));
for (int j = 0; j < deleted.length; j++) {
overwrittenResources.add(deleted[j]);
}
}
} else {
// no resources are being overwritten
// ensure the destination path exists
IPath parentPath = destination;
if (pathIncludesName) {
parentPath = destination.removeLastSegments(1);
}
IContainer generatedParent = generateContainers(parentPath);
source.copy(destinationPath, IResource.SHALLOW,
new SubProgressMonitor(monitor, 1));
// Record the copy. If we had to generate a parent
// folder, that should be recorded as part of the copy
if (generatedParent == null) {
resourcesAtDestination.add(getWorkspace().getRoot()
.findMember(destinationPath));
} else {
resourcesAtDestination.add(generatedParent);
}
}
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
monitor.done();
return (ResourceDescription[]) overwrittenResources
.toArray(new ResourceDescription[overwrittenResources.size()]);
}
/**
* Moves the resources to the given destination. This method can be called
* recursively to merge folders during folder move.
*
* @param resources
* the resources to be moved
* @param destination
* the destination path for the resources, relative to the
* workspace
* @param resourcesAtDestination
* A list used to record each moved resource.
* @param reverseDestinations
* A list used to record each moved resource's original location
* @param monitor
* the progress monitor used to show progress
* @param uiInfo
* the IAdaptable (or <code>null</code>) provided by the
* caller in order to supply UI information for prompting the
* user if necessary. When this parameter is not
* <code>null</code>, it contains an adapter for the
* org.eclipse.swt.widgets.Shell.class
* @param pathIncludesName
* a boolean that indicates whether the specified path includes
* the resource's name at the destination. If this value is
* <code>true</code>, the destination will contain the desired
* name of the resource (usually only desired when only one
* resource is being moved). If this value is <code>false</code>,
* each resource's name will be appended to the destination.
* @return an array of ResourceDescriptions describing any resources that
* were overwritten by the move operation
* @throws CoreException
* propagates any CoreExceptions thrown from the resources API
*/
static ResourceDescription[] move(IResource[] resources, IPath destination,
List resourcesAtDestination, List reverseDestinations,
IProgressMonitor monitor, IAdaptable uiInfo,
boolean pathIncludesName) throws CoreException {
monitor.beginTask("", resources.length); //$NON-NLS-1$
monitor
.setTaskName(UndoMessages.AbstractResourcesOperation_MovingResources);
List overwrittenResources = new ArrayList();
for (int i = 0; i < resources.length; i++) {
IResource source = resources[i];
IPath destinationPath;
if (pathIncludesName) {
destinationPath = destination;
} else {
destinationPath = destination.append(source.getName());
}
IWorkspaceRoot workspaceRoot = getWorkspaceRoot();
IResource existing = workspaceRoot.findMember(destinationPath);
if (source.getType() == IResource.FOLDER && existing != null) {
// The resource is a folder and it exists in the destination.
// Move its children to the existing destination.
if (source.isLinked() == existing.isLinked()) {
IResource[] children = ((IContainer) source).members();
ResourceDescription[] overwritten = move(children,
destinationPath, resourcesAtDestination,
reverseDestinations, new SubProgressMonitor(
monitor, 1), uiInfo, false);
// We don't record the moved resources since the recursive
// call has done so. Just record the overwrites.
for (int j = 0; j < overwritten.length; j++) {
overwrittenResources.add(overwritten[j]);
}
// Delete the source. No need to record it since it
// will get moved back.
delete(source, monitor, uiInfo, false, false);
} else {
// delete the destination folder, moving a linked folder
// over an unlinked one or vice versa. Fixes bug 28772.
ResourceDescription[] deleted = delete(
new IResource[] { existing },
new SubProgressMonitor(monitor, 0), uiInfo, false);
// Record the original path
reverseDestinations.add(source.getFullPath());
source.move(destinationPath, IResource.SHALLOW
| IResource.KEEP_HISTORY, new SubProgressMonitor(
monitor, 1));
// Record the resource at its destination
resourcesAtDestination.add(getWorkspace().getRoot()
.findMember(destinationPath));
for (int j = 0; j < deleted.length; j++) {
overwrittenResources.add(deleted[j]);
}
}
} else {
if (existing != null) {
if (source.isLinked() == existing.isLinked()) {
// Record the original path
reverseDestinations.add(source.getFullPath());
overwrittenResources.add(copyOverExistingResource(
source, existing, new SubProgressMonitor(
monitor, 1), uiInfo, true));
resourcesAtDestination.add(existing);
} else {
// Moving a linked resource over unlinked or vice
// versa. Can't use setContents here. Fixes bug 28772.
ResourceDescription[] deleted = delete(
new IResource[] { existing },
new SubProgressMonitor(monitor, 0), uiInfo,
false);
reverseDestinations.add(source.getFullPath());
source.move(destinationPath, IResource.SHALLOW
| IResource.KEEP_HISTORY,
new SubProgressMonitor(monitor, 1));
// Record the resource at its destination
resourcesAtDestination.add(getWorkspace().getRoot()
.findMember(destinationPath));
for (int j = 0; j < deleted.length; j++) {
overwrittenResources.add(deleted[j]);
}
}
} else {
// No resources are being overwritten.
// First record the source path
reverseDestinations.add(source.getFullPath());
// ensure the destination path exists
IPath parentPath = destination;
if (pathIncludesName) {
parentPath = destination.removeLastSegments(1);
}
IContainer generatedParent = generateContainers(parentPath);
source.move(destinationPath, IResource.SHALLOW
| IResource.KEEP_HISTORY, new SubProgressMonitor(
monitor, 1));
// Record the move. If we had to generate a parent
// folder, that should be recorded as part of the copy
if (generatedParent == null) {
resourcesAtDestination.add(getWorkspace().getRoot()
.findMember(destinationPath));
} else {
resourcesAtDestination.add(generatedParent);
}
}
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
monitor.done();
return (ResourceDescription[]) overwrittenResources
.toArray(new ResourceDescription[overwrittenResources.size()]);
}
/**
* Recreate the resources from the specified resource descriptions.
*
* @param resourcesToRecreate
* the ResourceDescriptions describing resources to be recreated
* @param monitor
* the progress monitor used to show progress
* @param uiInfo
* the IAdaptable (or <code>null</code>) provided by the
* caller in order to supply UI information for prompting the
* user if necessary. When this parameter is not
* <code>null</code>, it contains an adapter for the
* org.eclipse.swt.widgets.Shell.class
* @return an array of resources that were created
* @throws CoreException
* propagates any CoreExceptions thrown from the resources API
*/
static IResource[] recreate(ResourceDescription[] resourcesToRecreate,
IProgressMonitor monitor, IAdaptable uiInfo) throws CoreException {
final List exceptions = new ArrayList();
IResource[] resourcesToReturn = new IResource[resourcesToRecreate.length];
monitor.beginTask("", resourcesToRecreate.length); //$NON-NLS-1$
monitor
.setTaskName(UndoMessages.AbstractResourcesOperation_CreateResourcesProgress);
try {
for (int i = 0; i < resourcesToRecreate.length; ++i) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
try {
resourcesToReturn[i] = resourcesToRecreate[i]
.createResource(new SubProgressMonitor(monitor, 1));
} catch (CoreException e) {
exceptions.add(e);
}
}
IStatus result = WorkspaceUndoUtil.createResult(exceptions);
if (!result.isOK()) {
throw new CoreException(result);
}
} finally {
monitor.done();
}
return resourcesToReturn;
}
/**
* Delete the specified resources, returning a resource description that can
* be used to restore it.
*
* @param resourceToDelete
* the resource to be deleted
* @param monitor
* the progress monitor to use to show the operation's progress
* @param uiInfo
* the IAdaptable (or <code>null</code>) provided by the
* caller in order to supply UI information for prompting the
* user if necessary. When this parameter is not
* <code>null</code>, it contains an adapter for the
* org.eclipse.swt.widgets.Shell.class
* @param forceOutOfSyncDelete
* a boolean indicating whether a resource should be deleted even
* if it is out of sync with the file system
* @param deleteContent
* a boolean indicating whether project content should be deleted
* when a project resource is to be deleted
* @return a ResourceDescription that can be used to restore the deleted
* resource.
* @throws CoreException
* propagates any CoreExceptions thrown from the resources API
*/
static ResourceDescription delete(IResource resourceToDelete,
IProgressMonitor monitor, IAdaptable uiInfo,
boolean forceOutOfSyncDelete, boolean deleteContent)
throws CoreException {
ResourceDescription resourceDescription = ResourceDescription
.fromResource(resourceToDelete);
if (resourceToDelete.getType() == IResource.PROJECT) {
// it is a project
monitor
.setTaskName(UndoMessages.AbstractResourcesOperation_DeleteResourcesProgress);
IProject project = (IProject) resourceToDelete;
project.delete(deleteContent, forceOutOfSyncDelete, monitor);
} else {
// if it's not a project, just delete it
monitor.beginTask("", 2); //$NON-NLS-1$
monitor
.setTaskName(UndoMessages.AbstractResourcesOperation_DeleteResourcesProgress);
int updateFlags;
if (forceOutOfSyncDelete) {
updateFlags = IResource.KEEP_HISTORY | IResource.FORCE;
} else {
updateFlags = IResource.KEEP_HISTORY;
}
resourceToDelete.delete(updateFlags, new SubProgressMonitor(
monitor, 1));
resourceDescription.recordStateFromHistory(resourceToDelete,
new SubProgressMonitor(monitor, 1));
monitor.done();
}
return resourceDescription;
}
/*
* Copy the content of the specified resource to the existing resource,
* returning a ResourceDescription that can be used to restore the original
* content. Do nothing if the resources are not files.
*/
private static ResourceDescription copyOverExistingResource(
IResource source, IResource existing, IProgressMonitor monitor,
IAdaptable uiInfo, boolean deleteSourceFile) throws CoreException {
if (!(source instanceof IFile && existing instanceof IFile)) {
return null;
}
IFile file = (IFile) source;
IFile existingFile = (IFile) existing;
monitor
.beginTask(
UndoMessages.AbstractResourcesOperation_CopyingResourcesProgress,
3);
if (file != null && existingFile != null) {
if (validateEdit(file, existingFile, getShell(uiInfo))) {
// Remember the state of the existing file so it can be
// restored.
FileDescription fileDescription = new FileDescription(
existingFile);
// Reset the contents to that of the file being moved
existingFile.setContents(file.getContents(),
IResource.KEEP_HISTORY, new SubProgressMonitor(monitor,
1));
fileDescription.recordStateFromHistory(existingFile,
new SubProgressMonitor(monitor, 1));
// Now delete the source file if requested
// We don't need to remember anything about it, because
// any undo involving this operation will move the original
// content back to it.
if (deleteSourceFile) {
file.delete(IResource.KEEP_HISTORY, new SubProgressMonitor(
monitor, 1));
}
monitor.done();
return fileDescription;
}
}
monitor.done();
return null;
}
/*
* Check for existence of the specified path and generate any containers
* that do not yet exist. Return any generated containers, or null if no
* container had to be generated.
*/
private static IContainer generateContainers(IPath path)
throws CoreException {
IResource container;
if (path.segmentCount() == 0) {
// nothing to generate
return null;
}
container = getWorkspaceRoot().findMember(path);
// Nothing to generate because container exists
if (container != null) {
return null;
}
// Now make a non-existent handle representing the desired container
if (path.segmentCount() == 1) {
container = ResourcesPlugin.getWorkspace().getRoot().getProject(
path.segment(0));
} else {
container = ResourcesPlugin.getWorkspace().getRoot()
.getFolder(path);
}
ContainerDescription containerDescription = ContainerDescription
.fromContainer((IContainer) container);
container = containerDescription.createResourceHandle();
containerDescription.createExistentResourceFromHandle(container,
new NullProgressMonitor());
return (IContainer) container;
}
/*
* Ask the user whether the given resource should be deleted despite being
* out of sync with the file system.
*
* Return one of the IDialogConstants constants indicating which of the Yes,
* Yes to All, No, Cancel options has been selected by the user.
*/
private static int queryDeleteOutOfSync(IResource resource,
IAdaptable uiInfo) {
Shell shell = getShell(uiInfo);
final MessageDialog dialog = new MessageDialog(
shell,
UndoMessages.AbstractResourcesOperation_deletionMessageTitle,
null,
NLS
.bind(
UndoMessages.AbstractResourcesOperation_outOfSyncQuestion,
resource.getName()), MessageDialog.QUESTION,
new String[] { IDialogConstants.YES_LABEL,
IDialogConstants.YES_TO_ALL_LABEL,
IDialogConstants.NO_LABEL,
IDialogConstants.CANCEL_LABEL }, 0);
shell.getDisplay().syncExec(new Runnable() {
public void run() {
dialog.open();
}
});
int result = dialog.getReturnCode();
if (result == 0) {
return IDialogConstants.YES_ID;
}
if (result == 1) {
return IDialogConstants.YES_TO_ALL_ID;
}
if (result == 2) {
return IDialogConstants.NO_ID;
}
return IDialogConstants.CANCEL_ID;
}
/*
* Creates and return a result status appropriate for the given list of
* exceptions.
*/
private static IStatus createResult(List exceptions) {
if (exceptions.isEmpty()) {
return Status.OK_STATUS;
}
final int exceptionCount = exceptions.size();
if (exceptionCount == 1) {
return ((CoreException) exceptions.get(0)).getStatus();
}
CoreException[] children = (CoreException[]) exceptions
.toArray(new CoreException[exceptionCount]);
boolean outOfSync = false;
for (int i = 0; i < children.length; i++) {
if (children[i].getStatus().getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
outOfSync = true;
break;
}
}
String title = outOfSync ? UndoMessages.AbstractResourcesOperation_outOfSyncError
: UndoMessages.AbstractResourcesOperation_deletionExceptionMessage;
final MultiStatus multi = new MultiStatus(
IDEWorkbenchPlugin.IDE_WORKBENCH, 0, title, null);
for (int i = 0; i < exceptionCount; i++) {
CoreException exception = children[i];
IStatus status = exception.getStatus();
multi.add(new Status(status.getSeverity(), status.getPlugin(),
status.getCode(), status.getMessage(), exception));
}
return multi;
}
/*
* Return the workspace.
*/
private static IWorkspace getWorkspace() {
return ResourcesPlugin.getWorkspace();
}
/*
* Return the workspace root.
*/
private static IWorkspaceRoot getWorkspaceRoot() {
return getWorkspace().getRoot();
}
/*
* Validate the destination file if it is read-only and additionally the
* source file if both are read-only. Returns true if both files could be
* made writeable.
*/
private static boolean validateEdit(IFile source, IFile destination,
Shell shell) {
if (destination.isReadOnly()) {
IWorkspace workspace = WorkspaceUndoUtil.getWorkspace();
IStatus status;
if (source.isReadOnly()) {
status = workspace.validateEdit(new IFile[] { source,
destination }, shell);
} else {
status = workspace.validateEdit(new IFile[] { destination },
shell);
}
return status.isOK();
}
return true;
}
/**
* Return the shell described by the specified adaptable, or the active
* shell if no shell has been specified in the adaptable.
*
* @param uiInfo
* the IAdaptable (or <code>null</code>) provided by the
* caller in order to supply UI information for prompting the
* user if necessary. When this parameter is not
* <code>null</code>, it contains an adapter for the
* org.eclipse.swt.widgets.Shell.class
*
* @return the Shell that can be used to show information
*/
public static Shell getShell(IAdaptable uiInfo) {
if (uiInfo != null) {
Shell shell = (Shell) uiInfo.getAdapter(Shell.class);
if (shell != null) {
return shell;
}
}
return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
}
}