| /******************************************************************************* |
| * 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.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.ISchedulingRule; |
| import org.eclipse.core.runtime.jobs.MultiRule; |
| import org.eclipse.ui.actions.ReadOnlyStateChecker; |
| import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; |
| import org.eclipse.ui.internal.ide.undo.UndoMessages; |
| |
| /** |
| * An AbstractResourcesOperation represents an undoable operation that |
| * manipulates resources. It provides implementations for resource rename, |
| * delete, creation, and modification. It also assigns the workspace undo |
| * context as the undo context for operations of this type. Clients may call the |
| * public API from a background thread. |
| * |
| * This class is not intended to be subclassed by clients. |
| * |
| * @since 3.3 |
| * |
| */ |
| abstract class AbstractResourcesOperation extends AbstractWorkspaceOperation { |
| |
| /* |
| * The array of resource descriptions known by this operation to create or |
| * restore overwritten resources. |
| */ |
| protected ResourceDescription[] resourceDescriptions; |
| |
| /* |
| * Return true if the specified subResource is a descendant of the specified |
| * super resource. Used to remove descendants from the resource array when |
| * an operation is requested on a parent and its descendant. |
| */ |
| private static boolean isDescendantOf(IResource subResource, IResource superResource) { |
| return ! subResource.equals(superResource) && superResource.getFullPath().isPrefixOf(subResource.getFullPath()); |
| } |
| |
| |
| /** |
| * Create an Abstract Resources Operation |
| * |
| * @param resources |
| * the resources to be modified |
| * @param label |
| * the label of the operation |
| */ |
| AbstractResourcesOperation(IResource[] resources, String label) { |
| super(label); |
| this.addContext(WorkspaceUndoUtil.getWorkspaceUndoContext()); |
| |
| setTargetResources(resources); |
| } |
| |
| /** |
| * Create an Abstract Resources Operation |
| * |
| * @param resourceDescriptions |
| * the resourceDescriptions describing resources to be created |
| * @param label |
| * the label of the operation |
| */ |
| AbstractResourcesOperation(ResourceDescription[] resourceDescriptions, |
| String label) { |
| super(label); |
| addContext(WorkspaceUndoUtil.getWorkspaceUndoContext()); |
| setResourceDescriptions(resourceDescriptions); |
| } |
| |
| /** |
| * Delete any resources known by this operation. Store enough information to |
| * undo and redo the operation. |
| * |
| * @param monitor |
| * the progress monitor to use for the operation |
| * @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 |
| * <code>true</code> if the content of any known projects |
| * should be deleted along with the project. <code>false</code> |
| * if project content should not be deleted. |
| * @throws CoreException |
| * propagates any CoreExceptions thrown from the resources API |
| */ |
| protected void delete(IProgressMonitor monitor, IAdaptable uiInfo, |
| boolean deleteContent) throws CoreException { |
| setResourceDescriptions(WorkspaceUndoUtil.delete(resources, monitor, |
| uiInfo, deleteContent)); |
| setTargetResources(new IResource[0]); |
| } |
| |
| /** |
| * Recreate any resources known by this operation. Store enough information |
| * to undo and redo the operation. |
| * |
| * @param monitor |
| * the progress monitor to use for the operation |
| * @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 |
| * @throws CoreException |
| * propagates any CoreExceptions thrown from the resources API |
| */ |
| protected void recreate(IProgressMonitor monitor, IAdaptable uiInfo) |
| throws CoreException { |
| setTargetResources(WorkspaceUndoUtil.recreate(resourceDescriptions, |
| monitor, uiInfo)); |
| setResourceDescriptions(new ResourceDescription[0]); |
| } |
| |
| /** |
| * Compute the status for creating resources from the descriptions. A status |
| * severity of <code>OK</code> indicates that the create is likely to be |
| * successful. A status severity of <code>ERROR</code> indicates that the |
| * operation is no longer valid. Other status severities are open to |
| * interpretation by the caller. |
| * |
| * Note this method may be called on initial creation of a resource, or when |
| * a create or delete operation is being undone or redone. Therefore, this |
| * method should check conditions that can change over the life of the |
| * operation, such as the existence of the information needed to carry out |
| * the operation. One-time static checks should typically be done by the |
| * caller (such as the action that creates the operation) so that the user |
| * is not continually prompted or warned about conditions that were |
| * acceptable at the time of original execution. |
| * |
| * @param allowOverwrite |
| * a boolean that specifies whether resource creation should be |
| * allowed to overwrite an existent resource. |
| */ |
| protected IStatus computeCreateStatus(boolean allowOverwrite) { |
| if (resourceDescriptions == null || resourceDescriptions.length == 0) { |
| markInvalid(); |
| return getErrorStatus(UndoMessages.AbstractResourcesOperation_NotEnoughInfo); |
| } |
| for (int i = 0; i < resourceDescriptions.length; i++) { |
| // Check for enough info to restore the resource |
| if (!resourceDescriptions[i].isValid()) { |
| markInvalid(); |
| return getErrorStatus(UndoMessages.AbstractResourcesOperation_InvalidRestoreInfo); |
| } else if (!allowOverwrite |
| && resourceDescriptions[i].verifyExistence(false)) { |
| // overwrites are not allowed and the resource already exists |
| markInvalid(); |
| return getErrorStatus(UndoMessages.AbstractResourcesOperation_ResourcesAlreadyExist); |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| |
| /** |
| * Compute the status for deleting resources. A status severity of |
| * <code>OK</code> indicates that the delete is likely to be successful. A |
| * status severity of <code>ERROR</code> indicates that the operation is |
| * no longer valid. Other status severities are open to interpretation by |
| * the caller. |
| * |
| * Note this method may be called on initial deletion of a resource, or when |
| * a create or delete operation is being undone or redone. Therefore, this |
| * method should check conditions that can change over the life of the |
| * operation, such as the existence of the resources to be deleted. One-time |
| * static checks should typically be done by the caller (such as the action |
| * that creates the operation) so that the user is not continually prompted |
| * or warned about conditions that were acceptable at the time of original |
| * execution. |
| */ |
| protected IStatus computeDeleteStatus() { |
| if (resources == null || resources.length == 0) { |
| markInvalid(); |
| return getErrorStatus(UndoMessages.AbstractResourcesOperation_NotEnoughInfo); |
| } |
| if (!resourcesExist()) { |
| markInvalid(); |
| return getErrorStatus(UndoMessages.AbstractResourcesOperation_ResourcesDoNotExist); |
| } |
| return checkReadOnlyResources(resources); |
| } |
| |
| /** |
| * Check the specified resources for read only state, and return a |
| * status indicating whether the resources can be deleted. |
| */ |
| IStatus checkReadOnlyResources(IResource[] resourcesToCheck) { |
| // Check read only status if we are permitted |
| // to consult the user. |
| if (!quietCompute) { |
| ReadOnlyStateChecker checker = new ReadOnlyStateChecker( |
| getShell(null), |
| IDEWorkbenchMessages.DeleteResourceAction_title1, |
| IDEWorkbenchMessages.DeleteResourceAction_readOnlyQuestion); |
| checker.setIgnoreLinkedResources(true); |
| IResource[] approvedResources = checker |
| .checkReadOnlyResources(resourcesToCheck); |
| if (approvedResources.length == 0) { |
| // Consider this a cancelled redo. |
| return Status.CANCEL_STATUS; |
| } |
| // Redefine the redo to only include the approved ones. |
| setTargetResources(approvedResources); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| /** |
| * Set the array of resource descriptions describing resources to be |
| * restored when undoing or redoing this operation. |
| * |
| * @param descriptions |
| * the array of resource descriptions |
| */ |
| protected void setResourceDescriptions(ResourceDescription[] descriptions) { |
| if (descriptions == null) { |
| resourceDescriptions = new ResourceDescription[0]; |
| } else { |
| resourceDescriptions = descriptions; |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#appendDescriptiveText(java.lang.StringBuffer) |
| */ |
| protected void appendDescriptiveText(StringBuffer text) { |
| super.appendDescriptiveText(text); |
| text.append(" resourceDescriptions: "); //$NON-NLS-1$ |
| text.append(resourceDescriptions); |
| text.append('\''); |
| } |
| |
| /** |
| * Compute a scheduling rule for creating resources. |
| * |
| * @return a scheduling rule appropriate for creating the resources |
| * specified in the resource descriptions |
| */ |
| protected ISchedulingRule computeCreateSchedulingRule() { |
| ISchedulingRule[] ruleArray = new ISchedulingRule[resourceDescriptions.length * 3]; |
| |
| for (int i = 0; i < resourceDescriptions.length; i++) { |
| IResource resource = resourceDescriptions[i].createResourceHandle(); |
| // Need a rule for creating... |
| ruleArray[i * 3] = getWorkspaceRuleFactory().createRule(resource); |
| // ...and modifying |
| ruleArray[i * 3 + 1] = getWorkspaceRuleFactory().modifyRule( |
| resource); |
| // ...and changing the charset |
| ruleArray[i * 3 + 2] = getWorkspaceRuleFactory().charsetRule( |
| resource); |
| |
| } |
| return MultiRule.combine(ruleArray); |
| } |
| |
| /** |
| * Compute a scheduling rule for deleting resources. |
| * |
| * @return a scheduling rule appropriate for deleting the resources |
| * specified in the receiver. |
| */ |
| protected ISchedulingRule computeDeleteSchedulingRule() { |
| ISchedulingRule[] ruleArray = new ISchedulingRule[resources.length * 2]; |
| for (int i = 0; i < resources.length; i++) { |
| ruleArray[i * 2] = getWorkspaceRuleFactory().deleteRule( |
| resources[i]); |
| // we include a modify rule because we may have to open a project |
| // to record its resources before deleting it. |
| ruleArray[i * 2 + 1] = getWorkspaceRuleFactory().modifyRule( |
| resources[i]); |
| } |
| return MultiRule.combine(ruleArray); |
| |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#setTargetResources(org.eclipse.core.resources.IResource[]) |
| */ |
| protected void setTargetResources(IResource[] targetResources) { |
| // Remove any descendants if the parent has also |
| // been specified. |
| List subResources = new ArrayList(); |
| for (int i = 0; i < targetResources.length; i++) { |
| IResource subResource = targetResources[i]; |
| for (int j = 0; j < targetResources.length; j++) { |
| IResource superResource = targetResources[j]; |
| if (isDescendantOf(subResource, superResource)) |
| subResources.add(subResource); |
| } |
| } |
| IResource[] nestedResourcesRemoved = new IResource[targetResources.length |
| - subResources.size()]; |
| int j = 0; |
| for (int i = 0; i < targetResources.length; i++) { |
| if (!subResources.contains(targetResources[i])) { |
| nestedResourcesRemoved[j] = targetResources[i]; |
| j++; |
| } |
| } |
| super.setTargetResources(nestedResourcesRemoved); |
| } |
| } |