| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.team.internal.ccvs.ui.actions; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceStatus; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.team.core.RepositoryProvider; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.internal.ccvs.core.CVSException; |
| import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin; |
| import org.eclipse.team.internal.ccvs.core.CVSTag; |
| import org.eclipse.team.internal.ccvs.core.CVSTeamProvider; |
| import org.eclipse.team.internal.ccvs.core.ICVSFolder; |
| import org.eclipse.team.internal.ccvs.core.ICVSResource; |
| import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot; |
| import org.eclipse.team.internal.ccvs.core.resources.EclipseSynchronizer; |
| import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo; |
| import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo; |
| import org.eclipse.team.internal.ccvs.core.util.Util; |
| import org.eclipse.team.internal.ccvs.ui.Policy; |
| import org.eclipse.team.internal.ui.dialogs.IPromptCondition; |
| import org.eclipse.team.internal.ui.dialogs.PromptingDialog; |
| |
| /** |
| * This class represents an action performed on a local CVS workspace |
| */ |
| public abstract class WorkspaceAction extends CVSAction { |
| |
| public interface IProviderAction { |
| public IStatus execute(CVSTeamProvider provider, IResource[] resources, IProgressMonitor monitor) throws CVSException; |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.ui.actions.CVSAction#beginExecution(IAction) |
| */ |
| protected boolean beginExecution(IAction action) throws TeamException { |
| if (super.beginExecution(action)) { |
| // Ensure that the required sync info is loaded |
| if (requiresLocalSyncInfo()) { |
| // There is a possibility of the selection containing an orphaned subtree. |
| // If it does, they will be purged and enablement rechecked before the |
| // operation is performed. |
| handleOrphanedSubtrees(); |
| // Check enablement just in case the sync info wasn't loaded |
| if (!isEnabled()) { |
| MessageDialog.openInformation(getShell(), Policy.bind("CVSAction.disabledTitle"), Policy.bind("CVSAction.disabledMessage")); //$NON-NLS-1$ //$NON-NLS-2$ |
| return false; |
| } |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /* |
| * Determine if any of the selected resources are deascendants of |
| * an orphaned CVS subtree and if they are, purge the CVS folders. |
| */ |
| private boolean handleOrphanedSubtrees() { |
| // invoke the inherited method so that overlaps are maintained |
| IResource[] resources = getSelectedResources(); |
| for (int i = 0; i < resources.length; i++) { |
| IResource resource = resources[i]; |
| handleOrphanedSubtree(resource); |
| } |
| return false; |
| } |
| |
| /* |
| * Determine if the resource is a descendant of an orphaned subtree. |
| * If it is, purge the CVS folders of the subtree. |
| */ |
| private void handleOrphanedSubtree(IResource resource) { |
| try { |
| if (!CVSWorkspaceRoot.isSharedWithCVS(resource)) return ; |
| ICVSFolder folder; |
| if (resource.getType() == IResource.FILE) { |
| folder = CVSWorkspaceRoot.getCVSFolderFor(resource.getParent()); |
| } else { |
| folder = CVSWorkspaceRoot.getCVSFolderFor((IContainer)resource); |
| } |
| handleOrphanedSubtree(folder); |
| } catch (CVSException e) { |
| CVSProviderPlugin.log(e); |
| } |
| } |
| |
| /* |
| * Recursively check for and handle orphaned CVS folders |
| */ |
| private void handleOrphanedSubtree(final ICVSFolder folder) throws CVSException { |
| if (folder.getIResource().getType() == IResource.PROJECT) return; |
| if (CVSWorkspaceRoot.isOrphanedSubtree((IContainer)folder.getIResource())) { |
| try { |
| run(new IRunnableWithProgress() { |
| public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { |
| try { |
| folder.unmanage(null); |
| } catch (CVSException e) { |
| CVSProviderPlugin.log(e); |
| } |
| } |
| }, true, PROGRESS_WORKBENCH_WINDOW); |
| } catch (InvocationTargetException e) { |
| // Ignore this since we logged the one we care about above |
| } catch (InterruptedException e) { |
| throw new OperationCanceledException(); |
| } |
| } |
| handleOrphanedSubtree(folder.getParent()); |
| } |
| |
| /** |
| * Return true if the sync info is loaded for all selected resources. |
| * The purpose of this method is to allow enablement code to be as fast |
| * as possible. If the sync info is not loaded, the menu should be enabled |
| * and, if choosen, the action will verify that it is indeed enabled before |
| * performing the associated operation |
| */ |
| protected boolean isSyncInfoLoaded(IResource[] resources) throws CVSException { |
| return EclipseSynchronizer.getInstance().isSyncInfoLoaded(resources, getEnablementDepth()); |
| } |
| |
| /** |
| * Returns the resource depth of the action for use in determining if the required |
| * sync info is loaded. The default is IResource.DEPTH_INFINITE. Sunclasses can override |
| * as required. |
| */ |
| protected int getActionDepth() { |
| return IResource.DEPTH_INFINITE; |
| } |
| |
| /** |
| * Returns the resource depth of the action enablement for use in determining if the required |
| * sync info is loaded. The default is IResource.DEPTH_ZERO. Sunclasses can override |
| * as required. |
| */ |
| protected int getEnablementDepth() { |
| return IResource.DEPTH_ZERO; |
| } |
| |
| /** |
| * Ensure that the sync info for all the provided resources has been loaded. |
| * If an out-of-sync resource is found, prompt to refresh all the projects involved. |
| */ |
| protected boolean ensureSyncInfoLoaded(IResource[] resources) throws CVSException { |
| boolean keepTrying = true; |
| while (keepTrying) { |
| try { |
| EclipseSynchronizer.getInstance().ensureSyncInfoLoaded(resources, getActionDepth()); |
| keepTrying = false; |
| } catch (CVSException e) { |
| if (e.getStatus().getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) { |
| // determine the projects of the resources involved |
| Set projects = new HashSet(); |
| for (int i = 0; i < resources.length; i++) { |
| IResource resource = resources[i]; |
| projects.add(resource.getProject()); |
| } |
| // prompt to refresh |
| if (promptToRefresh(getShell(), (IResource[]) projects.toArray(new IResource[projects.size()]), e.getStatus())) { |
| for (Iterator iter = projects.iterator();iter.hasNext();) { |
| IProject project = (IProject) iter.next(); |
| try { |
| project.refreshLocal(IResource.DEPTH_INFINITE, null); |
| } catch (CoreException coreException) { |
| throw CVSException.wrapException(coreException); |
| } |
| } |
| } else { |
| return false; |
| } |
| } else { |
| throw e; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Override to ensure that the sync info is available before performing the |
| * real <code>isEnabled()</code> test. |
| * |
| * @see org.eclipse.team.internal.ui.actions.TeamAction#setActionEnablement(IAction) |
| */ |
| protected void setActionEnablement(IAction action) { |
| try { |
| boolean requires = requiresLocalSyncInfo(); |
| if (!requires || (requires && isSyncInfoLoaded(getSelectedResources()))) { |
| super.setActionEnablement(action); |
| } else { |
| // If the sync info is not loaded, enable the menu item |
| // Performing the action will ensure that the action should really |
| // be enabled before anything else is done |
| action.setEnabled(true); |
| } |
| } catch (CVSException e) { |
| // We couldn't determine if the sync info was loaded. |
| // Enable the action so that performing the action will |
| // reveal the error to the user. |
| action.setEnabled(true); |
| } |
| } |
| |
| /** |
| * Return true if the action requires the sync info for the selected resources. |
| * If the sync info is required, the real enablement code will only be run if |
| * the sync info is loaded from disc. Otherwise, the action is enabled and |
| * performing the action will load the sync info and verify that the action is truely |
| * enabled before doing anything else. |
| * |
| * This implementation returns <code>true</code>. Subclasses must override if they do |
| * not require the sync info of the selected resources. |
| * |
| * @return boolean |
| */ |
| protected boolean requiresLocalSyncInfo() { |
| return true; |
| } |
| |
| protected boolean promptToRefresh(final Shell shell, final IResource[] resources, final IStatus status) { |
| final boolean[] result = new boolean[] { false}; |
| Runnable runnable = new Runnable() { |
| public void run() { |
| Shell shellToUse = shell; |
| if (shell == null) { |
| shellToUse = new Shell(Display.getCurrent()); |
| } |
| String question; |
| if (resources.length == 1) { |
| question = Policy.bind("CVSAction.refreshQuestion", status.getMessage(), resources[0].getFullPath().toString()); //$NON-NLS-1$ |
| } else { |
| question = Policy.bind("CVSAction.refreshMultipleQuestion", status.getMessage()); //$NON-NLS-1$ |
| } |
| result[0] = MessageDialog.openQuestion(shellToUse, Policy.bind("CVSAction.refreshTitle"), question); //$NON-NLS-1$ |
| } |
| }; |
| Display.getDefault().syncExec(runnable); |
| return result[0]; |
| } |
| |
| /** |
| * Most CVS workspace actions modify the workspace and thus should |
| * save dirty editors. |
| * @see org.eclipse.team.internal.ccvs.ui.actions.CVSAction#needsToSaveDirtyEditors() |
| */ |
| protected boolean needsToSaveDirtyEditors() { |
| return true; |
| } |
| |
| /** |
| * The action is enabled for the appropriate resources. This method checks |
| * that: |
| * <ol> |
| * <li>there is no overlap between a selected file and folder (overlapping |
| * folders is allowed because of logical vs. physical mapping problem in |
| * views) |
| * <li>the state of the resources match the conditions provided by: |
| * <ul> |
| * <li>isEnabledForIgnoredResources() |
| * <li>isEnabledForManagedResources() |
| * <li>isEnabledForUnManagedResources() (i.e. not ignored and not managed) |
| * </ul> |
| * </ol> |
| * @see TeamAction#isEnabled() |
| */ |
| protected boolean isEnabled() throws TeamException { |
| |
| // invoke the inherited method so that overlaps are maintained |
| IResource[] resources = super.getSelectedResources(); |
| |
| // disable if no resources are selected |
| if(resources.length==0) return false; |
| |
| // disable properly for single resource enablement |
| if (!isEnabledForMultipleResources() && resources.length != 1) return false; |
| |
| // validate enabled for each resource in the selection |
| List folderPaths = new ArrayList(); |
| List filePaths = new ArrayList(); |
| for (int i = 0; i < resources.length; i++) { |
| IResource resource = resources[i]; |
| |
| // only enable for accessible resources |
| if(resource.getType() == IResource.PROJECT) { |
| if (! resource.isAccessible()) return false; |
| } |
| |
| // no CVS actions are enabled if the selection contains a linked resource |
| if (CVSWorkspaceRoot.isLinkedResource(resource)) return false; |
| |
| // only enable for resources in a project shared with CVS |
| if(RepositoryProvider.getProvider(resource.getProject(), CVSProviderPlugin.getTypeId()) == null) { |
| return false; |
| } |
| |
| // collect files and folders separately to check for overlap later |
| IPath resourceFullPath = resource.getFullPath(); |
| if(resource.getType() == IResource.FILE) { |
| filePaths.add(resourceFullPath); |
| } else { |
| folderPaths.add(resourceFullPath); |
| } |
| |
| // ensure that resource management state matches what the action requires |
| ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource); |
| if (!isEnabledForCVSResource(cvsResource)) { |
| return false; |
| } |
| } |
| // Ensure that there is no overlap between files and folders |
| // NOTE: folder overlap must be allowed because of logical vs. physical |
| if(!folderPaths.isEmpty()) { |
| for (Iterator fileIter = filePaths.iterator(); fileIter.hasNext();) { |
| IPath resourcePath = (IPath) fileIter.next(); |
| for (Iterator it = folderPaths.iterator(); it.hasNext();) { |
| IPath folderPath = (IPath) it.next(); |
| if (folderPath.isPrefixOf(resourcePath)) { |
| return false; |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Method isEnabledForCVSResource. |
| * @param cvsResource |
| * @return boolean |
| */ |
| protected boolean isEnabledForCVSResource(ICVSResource cvsResource) throws CVSException { |
| boolean managed = false; |
| boolean ignored = false; |
| boolean added = false; |
| if (cvsResource.isIgnored()) { |
| ignored = true; |
| } else if (cvsResource.isFolder()) { |
| managed = ((ICVSFolder)cvsResource).isCVSFolder(); |
| } else { |
| ResourceSyncInfo info = cvsResource.getSyncInfo(); |
| managed = info != null; |
| if (managed) added = info.isAdded(); |
| } |
| if (managed && ! isEnabledForManagedResources()) return false; |
| if ( ! managed && ! isEnabledForUnmanagedResources()) return false; |
| if ( ignored && ! isEnabledForIgnoredResources()) return false; |
| if (added && ! isEnabledForAddedResources()) return false; |
| if ( ! cvsResource.exists() && ! isEnabledForNonExistantResources()) return false; |
| return true; |
| } |
| |
| /** |
| * Method isEnabledForIgnoredResources. |
| * @return boolean |
| */ |
| protected boolean isEnabledForIgnoredResources() { |
| return false; |
| } |
| |
| /** |
| * Method isEnabledForUnmanagedResources. |
| * @return boolean |
| */ |
| protected boolean isEnabledForUnmanagedResources() { |
| return false; |
| } |
| |
| /** |
| * Method isEnabledForManagedResources. |
| * @return boolean |
| */ |
| protected boolean isEnabledForManagedResources() { |
| return true; |
| } |
| |
| /** |
| * Method isEnabledForAddedResources. |
| * @return boolean |
| */ |
| protected boolean isEnabledForAddedResources() { |
| return true; |
| } |
| |
| /** |
| * Method isEnabledForAddedResources. |
| * @return boolean |
| */ |
| protected boolean isEnabledForMultipleResources() { |
| return true; |
| } |
| |
| /** |
| * Method isEnabledForNonExistantResources. |
| * @return boolean |
| */ |
| protected boolean isEnabledForNonExistantResources() { |
| return false; |
| } |
| |
| /** |
| * Method getNonOverlapping ensures that a resource is not covered more than once. |
| * @param resources |
| * @return IResource[] |
| */ |
| public static IResource[] getNonOverlapping(IResource[] resources) { |
| // Sort the resources so the shortest paths are first |
| List sorted = new ArrayList(); |
| sorted.addAll(Arrays.asList(resources)); |
| Collections.sort(sorted, new Comparator() { |
| public int compare(Object arg0, Object arg1) { |
| IResource resource0 = (IResource) arg0; |
| IResource resource1 = (IResource) arg1; |
| return resource0.getFullPath().segmentCount() - resource1.getFullPath().segmentCount(); |
| } |
| public boolean equals(Object arg0) { |
| return false; |
| } |
| }); |
| // Collect all non-overlapping resources |
| List coveredPaths = new ArrayList(); |
| for (Iterator iter = sorted.iterator(); iter.hasNext();) { |
| IResource resource = (IResource) iter.next(); |
| IPath resourceFullPath = resource.getFullPath(); |
| boolean covered = false; |
| for (Iterator it = coveredPaths.iterator(); it.hasNext();) { |
| IPath path = (IPath) it.next(); |
| if(path.isPrefixOf(resourceFullPath)) { |
| covered = true; |
| } |
| } |
| if (covered) { |
| // if the resource is covered by a parent, remove it |
| iter.remove(); |
| } else { |
| // if the resource is a non-covered folder, add it to the covered paths |
| if (resource.getType() == IResource.FOLDER) { |
| coveredPaths.add(resource.getFullPath()); |
| } |
| } |
| } |
| return (IResource[]) sorted.toArray(new IResource[sorted.size()]); |
| } |
| |
| /** |
| * Override to ensure that the selected resources so not overlap. |
| * This method assumes that all actions are deep. |
| * |
| * @see org.eclipse.team.internal.ui.actions.TeamAction#getSelectedResources() |
| */ |
| protected IResource[] getSelectedResources() { |
| return getNonOverlapping(super.getSelectedResources()); |
| } |
| |
| protected void executeProviderAction(IProviderAction action, IResource[] resources, IProgressMonitor monitor) throws InvocationTargetException { |
| Hashtable table = getProviderMapping(resources); |
| Set keySet = table.keySet(); |
| monitor.beginTask(null, keySet.size() * 1000); |
| Iterator iterator = keySet.iterator(); |
| |
| while (iterator.hasNext()) { |
| IProgressMonitor subMonitor = new SubProgressMonitor(monitor, 1000); |
| CVSTeamProvider provider = (CVSTeamProvider)iterator.next(); |
| List list = (List)table.get(provider); |
| IResource[] providerResources = (IResource[])list.toArray(new IResource[list.size()]); |
| try { |
| addStatus(action.execute(provider, providerResources, subMonitor)); |
| } catch (CVSException e) { |
| throw new InvocationTargetException(e); |
| } |
| |
| } |
| } |
| |
| protected void executeProviderAction(IProviderAction action, IProgressMonitor monitor) throws InvocationTargetException { |
| executeProviderAction(action, getSelectedResources(), monitor); |
| } |
| |
| /** |
| * Given the current selection this method returns a text label that can |
| * be shown to the user that reflects the tags in the current selection. |
| * These can be used in the <b>Compare With</b> and <b>Replace With</b> actions. |
| */ |
| protected String calculateActionTagValue() { |
| try { |
| IResource[] resources = getSelectedResources(); |
| CVSTag commonTag = null; |
| boolean sameTagType = true; |
| boolean multipleSameNames = true; |
| |
| for (int i = 0; i < resources.length; i++) { |
| ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resources[i]); |
| CVSTag tag = null; |
| if(cvsResource.isFolder()) { |
| FolderSyncInfo info = ((ICVSFolder)cvsResource).getFolderSyncInfo(); |
| if(info != null) { |
| tag = info.getTag(); |
| } |
| if (tag != null && tag.getType() == CVSTag.BRANCH) { |
| tag = Util.getAccurateFolderTag(resources[i], tag); |
| } |
| } else { |
| tag = Util.getAccurateFileTag(cvsResource); |
| } |
| if(tag == null) { |
| tag = new CVSTag(); |
| } |
| if(commonTag == null) { |
| commonTag = tag; |
| } else if(!commonTag.equals(tag)) { |
| if(commonTag.getType() != tag.getType()) { |
| sameTagType = false; |
| } |
| if(!commonTag.getName().equals(tag.getName())) { |
| multipleSameNames = false; |
| } |
| } |
| } |
| |
| // set text to default |
| String actionText = Policy.bind("ReplaceWithLatestAction.multipleTags"); //$NON-NLS-1$ |
| if(commonTag != null) { |
| int tagType = commonTag.getType(); |
| String tagName = commonTag.getName(); |
| // multiple tag names but of the same type |
| if(sameTagType && !multipleSameNames) { |
| if(tagType == CVSTag.BRANCH) { |
| actionText = Policy.bind("ReplaceWithLatestAction.multipleBranches"); //$NON-NLS-1$ |
| } else { |
| actionText = Policy.bind("ReplaceWithLatestAction.multipleVersions"); //$NON-NLS-1$ |
| } |
| // same tag names and types |
| } else if(sameTagType && multipleSameNames) { |
| if(tagType == CVSTag.BRANCH) { |
| actionText = Policy.bind("ReplaceWithLatestAction.singleBranch", tagName); //$NON-NLS-1$ |
| } else if(tagType == CVSTag.VERSION){ |
| actionText = Policy.bind("ReplaceWithLatestAction.singleVersion", tagName); //$NON-NLS-1$ |
| } else if(tagType == CVSTag.HEAD) { |
| actionText = Policy.bind("ReplaceWithLatestAction.singleHEAD", tagName); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| return actionText; |
| } catch (CVSException e) { |
| // silently ignore |
| return Policy.bind("ReplaceWithLatestAction.multipleTags"); //$NON-NLS-1$ |
| } |
| } |
| |
| protected IResource[] checkOverwriteOfDirtyResources(IResource[] resources, IProgressMonitor monitor) throws CVSException, InterruptedException { |
| List dirtyResources = new ArrayList(); |
| IResource[] selectedResources = getSelectedResources(); |
| |
| try { |
| monitor = Policy.monitorFor(monitor); |
| monitor.beginTask(null, selectedResources.length * 100); |
| monitor.setTaskName(Policy.bind("ReplaceWithAction.calculatingDirtyResources")); //$NON-NLS-1$ |
| for (int i = 0; i < selectedResources.length; i++) { |
| IResource resource = selectedResources[i]; |
| ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource); |
| if(cvsResource.isModified(Policy.subMonitorFor(monitor, 100))) { |
| dirtyResources.add(resource); |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| |
| PromptingDialog dialog = new PromptingDialog(getShell(), selectedResources, |
| getPromptCondition((IResource[]) dirtyResources.toArray(new IResource[dirtyResources.size()])), Policy.bind("ReplaceWithAction.confirmOverwrite"));//$NON-NLS-1$ |
| return dialog.promptForMultiple(); |
| } |
| |
| /** |
| * This is a helper for the CVS UI automated tests. It allows the tests to ignore prompting dialogs. |
| * @param resources |
| */ |
| protected IPromptCondition getPromptCondition(IResource[] resources) { |
| return getOverwriteLocalChangesPrompt(resources); |
| } |
| } |