/******************************************************************************* * Copyright (c) 2000, 2004 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.*; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; 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.*; 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.actions.TeamAction; 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_BUSYCURSOR); } 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 isEnabled() 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 true. 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: *
    *
  1. there is no overlap between a selected file and folder (overlapping * folders is allowed because of logical vs. physical mapping problem in * views) *
  2. the state of the resources match the conditions provided by: * *
* @see TeamAction#isEnabled() */ protected boolean isEnabled() throws TeamException { // allow the super to decide enablement. if the super doesn't know it will return false. boolean enabled = super.isEnabled(); if(enabled) return true; // invoke the inherited method so that overlaps are maintained IResource[] resources = getSelectedResourcesWithOverlap(); // 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; } /** * Return all the selected resources even if some overlap with others. * @return all the selected resources even if some overlap with others. */ protected IResource[] getSelectedResourcesWithOverlap() { return super.getSelectedResources(); } /** * 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 final IResource[] getSelectedResources() { return getNonOverlapping(getSelectedResourcesWithOverlap()); } 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 Compare With and Replace With 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); } }