blob: 594210167d663268c8be7e8c5fbcdc0e39a443b7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.team.internal.ccvs.ui.operations;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IAction;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.mapping.ISynchronizationScope;
import org.eclipse.team.core.mapping.ISynchronizationScopeManager;
import org.eclipse.team.core.mapping.provider.SynchronizationScopeManager;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.Command;
import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption;
import org.eclipse.team.internal.ccvs.core.client.Session;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.ui.CVSUIPlugin;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.team.internal.ui.mapping.BuildScopeOperation;
import org.eclipse.ui.IWorkbenchPart;
/**
* Performs a CVS operation on multiple repository providers
*/
public abstract class RepositoryProviderOperation extends CVSOperation {
/**
* Flag to indicate whether models are to be consulted when building
* the scope. This is provided for testing purposes and is not expected
* to be used otherwise.
*/
public static boolean consultModelsWhenBuildingScope = true;
private ISynchronizationScopeManager manager;
private final ResourceMapping[] selectedMappings;
/**
* Interface that is available to subclasses which identifies
* the depth for various resources. The files will be included
* in whichever group (deep or shallow) has resources.
*/
public interface ICVSTraversal {
IResource[] getShallowResources();
IResource[] getDeepResources();
IResource[] getNontraversedFolders();
}
/*
* A map entry for a provider that divides the traversals to be performed by depth.
* There are really only
*/
private static class TraversalMapEntry implements ICVSTraversal {
// The provider for this entry
RepositoryProvider provider;
// Files are always shallow
List<IResource> files = new ArrayList<>();
// Not sure what to do with zero depth folders but we'll record them
List<IResource> zeroFolders = new ArrayList<>();
// Non-recursive folder (-l)
List<IResource> shallowFolders = new ArrayList<>();
// Recursive folders (-R)
List<IResource> deepFolders = new ArrayList<>();
public TraversalMapEntry(RepositoryProvider provider) {
this.provider = provider;
}
/**
* Add the resources from the traversals to the entry
* @param traversals the traversals
*/
public void add(ResourceTraversal[] traversals) {
for (int i = 0; i < traversals.length; i++) {
ResourceTraversal traversal = traversals[i];
add(traversal);
}
}
/**
* Add the resources from the traversal to the entry
* @param traversal the traversal
*/
public void add(ResourceTraversal traversal) {
IResource[] resources = traversal.getResources();
for (int i = 0; i < resources.length; i++) {
IResource resource = resources[i];
if (resource.getProject().equals(provider.getProject())) {
if (resource.getType() == IResource.FILE) {
files.add(resource);
} else {
switch (traversal.getDepth()) {
case IResource.DEPTH_ZERO:
zeroFolders.add(resource);
break;
case IResource.DEPTH_ONE:
shallowFolders.add(resource);
break;
case IResource.DEPTH_INFINITE:
deepFolders.add(resource);
break;
default:
deepFolders.add(resource);
}
}
}
}
}
/**
* Return the resources that can be included in a shallow operation.
* Include files with the shallow resources if there are shallow folders
* or if there are no shallow or deep folders.
* @return the resources that can be included in a shallow operation
*/
@Override
public IResource[] getShallowResources() {
if (shallowFolders.isEmpty() && deepFolders.isEmpty() && !files.isEmpty()) {
return files.toArray(new IResource[files.size()]);
}
if (!shallowFolders.isEmpty()) {
if (files.isEmpty()) {
return shallowFolders.toArray(new IResource[shallowFolders.size()]);
}
List<IResource> result = new ArrayList<>();
result.addAll(shallowFolders);
result.addAll(files);
return result.toArray(new IResource[result.size()]);
}
return new IResource[0];
}
/**
* Return the resources to be included in a deep operation.
* If there are no shallow folders, this will include any files.
* @return
*/
@Override
public IResource[] getDeepResources() {
if (deepFolders.isEmpty())
return new IResource[0];
if (!shallowFolders.isEmpty())
return deepFolders.toArray(new IResource[deepFolders.size()]);
List<IResource> result = new ArrayList<>();
result.addAll(deepFolders);
result.addAll(files);
return result.toArray(new IResource[result.size()]);
}
/**
* Return the folders that are depth zero
*/
@Override
public IResource[] getNontraversedFolders() {
return zeroFolders.toArray(new IResource[zeroFolders.size()]);
}
}
/**
* Convert the provided resources to one or more resource mappers
* that traverse the elements deeply. The model element of the resource
* mappers will be an IStructuredSelection.
* @param resources the resources
* @return a resource mappers that traverses the resources
*/
public static ResourceMapping[] asResourceMappers(final IResource[] resources) {
return asResourceMappers(resources, IResource.DEPTH_INFINITE);
}
/**
* Convert the provided resources to one or more resource mappers
* that traverse the elements deeply. The model element of the resource
* mappers will be an IStructuredSelection.
* @param resources the resources
* @return a resource mappers that traverses the resources
*/
public static ResourceMapping[] asResourceMappers(final IResource[] resources, int depth) {
return WorkspaceResourceMapper.asResourceMappers(resources, depth);
}
public RepositoryProviderOperation(IWorkbenchPart part, final IResource[] resources) {
this(part, asResourceMappers(resources));
}
public RepositoryProviderOperation(IWorkbenchPart part, ResourceMapping[] selectedMappings) {
super(part);
this.selectedMappings = selectedMappings;
}
@Override
public void execute(IProgressMonitor monitor) throws CVSException, InterruptedException {
try {
monitor.beginTask(null, 100);
buildScope(monitor);
Map table = getProviderTraversalMapping(Policy.subMonitorFor(monitor, 30));
execute(table, Policy.subMonitorFor(monitor, 30));
} catch (CoreException e) {
throw CVSException.wrapException(e);
} finally {
monitor.done();
}
}
@Override
protected void endOperation() throws CVSException {
if (manager != null) {
manager.dispose();
manager = null;
}
super.endOperation();
}
public ISynchronizationScope buildScope(IProgressMonitor monitor) throws InterruptedException, CVSException {
if (manager == null) {
manager = createScopeManager(consultModelsWhenBuildingScope && consultModelsForMappings());
BuildScopeOperation op = new BuildScopeOperation(getPart(), manager);
try {
op.run(monitor);
} catch (InvocationTargetException e) {
throw CVSException.wrapException(e);
}
}
return manager.getScope();
}
/**
* Create the scope manager to be used by this operation.
* @param consultModels whether models should be consulted to include additional mappings
* @return a scope manager
*/
protected SynchronizationScopeManager createScopeManager(boolean consultModels) {
return new SynchronizationScopeManager(getJobName(), getSelectedMappings(), getResourceMappingContext(), consultModels);
}
private void execute(Map providerTraversal, IProgressMonitor monitor) throws CVSException, InterruptedException {
Set keySet = providerTraversal.keySet();
monitor.beginTask(null, keySet.size() * 1000);
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
CVSTeamProvider provider = (CVSTeamProvider)iterator.next();
monitor.setTaskName(getTaskName(provider));
TraversalMapEntry entry = (TraversalMapEntry)providerTraversal.get(provider);
execute(provider, entry, Policy.subMonitorFor(monitor, 1000));
}
}
/**
* Execute the operation on the given set of traversals
* @param provider
* @param entry
* @param subMonitor
* @throws CVSException
* @throws InterruptedException
*/
protected void execute(CVSTeamProvider provider, ICVSTraversal entry, IProgressMonitor monitor) throws CVSException, InterruptedException {
IResource[] deepResources = entry.getDeepResources();
IResource[] shallowResources = entry.getShallowResources();
IResource[] nontraversedFolders = entry.getNontraversedFolders();
try {
monitor.beginTask(getTaskName(provider), (deepResources.length > 0 ? 100 : 0) + (shallowResources.length > 0 ? 100 : 0) + (nontraversedFolders.length > 0 ? 10 : 0));
if (deepResources.length == 0 && shallowResources.length == 0 && nontraversedFolders.length == 0)
return;
final ISchedulingRule rule = getSchedulingRule(provider);
try {
Job.getJobManager().beginRule(rule, monitor);
if (deepResources.length > 0)
execute(provider, deepResources, true /* recurse */, Policy.subMonitorFor(monitor, 100));
if (shallowResources.length > 0)
execute(provider, shallowResources, false /* recurse */, Policy.subMonitorFor(monitor, 100));
if (nontraversedFolders.length > 0) {
handleNontraversedFolders(provider, nontraversedFolders, Policy.subMonitorFor(monitor, 10));
}
} finally {
Job.getJobManager().endRule(rule);
}
} finally {
monitor.done();
}
}
/**
* Handle any non-traversed (depth-zero) folders that were in the logical modle that primed this operation.
* @param provider the repository provider associated with the project containing the folders
* @param nontraversedFolders the folders
* @param monitor a progress monitor
*/
protected void handleNontraversedFolders(CVSTeamProvider provider, IResource[] nontraversedFolders, IProgressMonitor monitor) throws CVSException {
// Default is do nothing
}
/**
* Return the taskname to be shown in the progress monitor while operating
* on the given provider.
* @param provider the provider being processed
* @return the taskname to be shown in the progress monitor
*/
protected abstract String getTaskName(CVSTeamProvider provider);
/**
* Retgurn the scheduling rule to be obtained before work
* begins on the given provider. By default, it is the provider's project.
* This can be changed by subclasses.
* @param provider
* @return
*/
protected ISchedulingRule getSchedulingRule(CVSTeamProvider provider) {
return provider.getProject();
}
/*
* Helper method. Return a Map mapping provider to a list of resources
* shared with that provider.
*/
Map getProviderTraversalMapping(IProgressMonitor monitor) throws CoreException {
Map<RepositoryProvider, TraversalMapEntry> result = new HashMap<>();
ResourceMapping[] mappings = getScope().getMappings();
for (int j = 0; j < mappings.length; j++) {
ResourceMapping mapping = mappings[j];
IProject[] projects = mapping.getProjects();
ResourceTraversal[] traversals = getScope().getTraversals(mapping);
for (int k = 0; k < projects.length; k++) {
IProject project = projects[k];
RepositoryProvider provider = RepositoryProvider.getProvider(project, CVSProviderPlugin.getTypeId());
if (provider != null) {
TraversalMapEntry entry = result.get(provider);
if (entry == null) {
entry = new TraversalMapEntry(provider);
result.put(provider, entry);
}
entry.add(traversals);
}
}
}
return result;
}
/**
* Return the resource mapping context that is to be used by this operation.
* By default, <code>null</code> is returned but subclasses may override
* to provide a specific context.
* @return the resource mapping context for this operation
*/
protected ResourceMappingContext getResourceMappingContext() {
return ResourceMappingContext.LOCAL_CONTEXT;
}
/**
* Execute the operation on the resources for the given provider.
* @param provider the provider for the project that contains the resources
* @param resources the resources to be operated on
* @param recurse whether the operation is deep or shallow
* @param monitor a progress monitor
* @throws CVSException
* @throws InterruptedException
*/
protected abstract void execute(CVSTeamProvider provider, IResource[] resources, boolean recurse, IProgressMonitor monitor) throws CVSException, InterruptedException;
/**
* Return the local options for this operation including the
* option to provide the requested traversal.
* @param recurse deep or shallow
* @return the local options for the operation
*/
protected LocalOption[] getLocalOptions(boolean recurse) {
if (!recurse) {
return new LocalOption[] { Command.DO_NOT_RECURSE };
}
return Command.NO_LOCAL_OPTIONS;
}
protected ICVSResource[] getCVSArguments(IResource[] resources) {
ICVSResource[] cvsResources = new ICVSResource[resources.length];
for (int i = 0; i < cvsResources.length; i++) {
cvsResources[i] = CVSWorkspaceRoot.getCVSResourceFor(resources[i]);
}
return cvsResources;
}
/*
* Get the arguments to be passed to a commit or update
*/
protected String[] getStringArguments(IResource[] resources) throws CVSException {
List<String> arguments = new ArrayList<>(resources.length);
for (int i=0;i<resources.length;i++) {
IPath cvsPath = resources[i].getFullPath().removeFirstSegments(1);
if (cvsPath.segmentCount() == 0) {
arguments.add(Session.CURRENT_LOCAL_FOLDER);
} else {
arguments.add(cvsPath.toString());
}
}
return arguments.toArray(new String[arguments.size()]);
}
protected ICVSRepositoryLocation getRemoteLocation(CVSTeamProvider provider) throws CVSException {
CVSWorkspaceRoot workspaceRoot = provider.getCVSWorkspaceRoot();
return workspaceRoot.getRemoteLocation();
}
protected ICVSFolder getLocalRoot(CVSTeamProvider provider) throws CVSException {
CVSWorkspaceRoot workspaceRoot = provider.getCVSWorkspaceRoot();
return workspaceRoot.getLocalRoot();
}
/**
* Update the workspace subscriber for an update operation performed on the
* given resources. After an update, the remote tree is flushed in order
* to ensure that stale incoming additions are removed. This need only
* be done for folders. At the time of writing, all update operations
* are deep so the flush is deep as well.
* @param provider the provider (project) for all the given resources
* @param resources the resources that were updated
* @param recurse
* @param monitor a progress monitor
*/
protected void updateWorkspaceSubscriber(CVSTeamProvider provider, ICVSResource[] resources, boolean recurse, IProgressMonitor monitor) {
CVSWorkspaceSubscriber s = CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber();
monitor.beginTask(null, 100 * resources.length);
for (int i = 0; i < resources.length; i++) {
ICVSResource resource = resources[i];
if (resource.isFolder()) {
try {
s.updateRemote(provider, (ICVSFolder)resource, recurse, Policy.subMonitorFor(monitor, 100));
} catch (TeamException e) {
// Just log the error and continue
CVSUIPlugin.log(e);
}
} else {
monitor.worked(100);
}
}
}
@Override
public boolean isKeepOneProgressServiceEntry() {
// Keep the last repository provider operation in the progress service
return true;
}
@Override
protected IAction getGotoAction() {
return getShowConsoleAction();
}
/**
* Return the root resources for all the traversals of this operation.
* This method may only be invoked after {@link #buildScope(IProgressMonitor) }.
* @return the root resources for all the traversals of this operation
* @throws CoreException
*/
protected IResource[] getTraversalRoots() {
List<IResource> result = new ArrayList<>();
ResourceTraversal[] traversals = getTraversals();
for (int i = 0; i < traversals.length; i++) {
ResourceTraversal traversal = traversals[i];
result.addAll(Arrays.asList(traversal.getResources()));
}
return result.toArray(new IResource[result.size()]);
}
/**
* Return the traversals that will be used by this operation.
* This method can only be called after {@link #buildScope(IProgressMonitor) }.
* @return the traversals that will be used by this operation
* @throws CoreException
*/
public ResourceTraversal[] getTraversals() {
return getScope().getTraversals();
}
public boolean consultModelsForMappings() {
return true;
}
public ResourceMapping[] getSelectedMappings() {
return selectedMappings;
}
public ISynchronizationScope getScope() {
return manager.getScope();
}
public ISynchronizationScopeManager getScopeManager() {
return manager;
}
}