Copyright (c) 2000, 2017 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
/******************************************************************************* * Copyright (c) 2000, 2017 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.core.mapping.provider; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.mapping.IModelProviderDescriptor; import org.eclipse.core.resources.mapping.ModelProvider; import org.eclipse.core.resources.mapping.ResourceMapping; import org.eclipse.core.resources.mapping.ResourceMappingContext; import org.eclipse.core.resources.mapping.ResourceTraversal; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.PlatformObject; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.MultiRule; import org.eclipse.team.core.mapping.ISynchronizationScope; import org.eclipse.team.core.mapping.ISynchronizationScopeManager; import org.eclipse.team.core.subscribers.SubscriberScopeManager; import org.eclipse.team.internal.core.Policy; import org.eclipse.team.internal.core.mapping.CompoundResourceTraversal; import org.eclipse.team.internal.core.mapping.ResourceMappingScope; import org.eclipse.team.internal.core.mapping.ScopeChangeEvent; import org.eclipse.team.internal.core.mapping.ScopeManagerEventHandler;
Class for translating a set of ResourceMapping objects representing a view selection into the complete set of resources to be operated on.

Here's a summary of the scope generation algorithm:

  1. Obtain selected mappings
  2. Project mappings onto resources using the appropriate context(s) in order to obtain a set of ResourceTraverals
  3. Determine what model providers are interested in the targeted resources
  4. From those model providers, obtain the set of affected resource mappings
  5. If the original set is the same as the new set, we are done.
  6. If the set differs from the original selection, rerun the mapping process for any new mappings
    • Only need to query model providers for mappings for new resources
    • Keep repeating until no new mappings or resources are added

This implementation does not involve participants in the scope management process. It is up to subclasses that wish to support a longer life cycle for scopes to provide for participation. For example, the SubscriberScopeManager class includes participates in the scope management process.

See Also:
Since:3.2
/** * Class for translating a set of <code>ResourceMapping</code> objects * representing a view selection into the complete set of resources to be * operated on. * <p> * Here's a summary of the scope generation algorithm: * <ol> * <li>Obtain selected mappings * <li>Project mappings onto resources using the appropriate context(s) in * order to obtain a set of ResourceTraverals * <li>Determine what model providers are interested in the targeted resources * <li>From those model providers, obtain the set of affected resource mappings * <li>If the original set is the same as the new set, we are done. * <li>If the set differs from the original selection, rerun the mapping * process for any new mappings * <ul> * <li>Only need to query model providers for mappings for new resources * <li>Keep repeating until no new mappings or resources are added * </ul> * </ol> * <p> * This implementation does not involve participants in the scope management * process. It is up to subclasses that wish to support a longer life cycle for * scopes to provide for participation. For example, the * {@link SubscriberScopeManager} class includes participates in the scope * management process. * * @see org.eclipse.core.resources.mapping.ResourceMapping * @see SubscriberScopeManager * * @since 3.2 */
public class SynchronizationScopeManager extends PlatformObject implements ISynchronizationScopeManager { private static final int MAX_ITERATION = 10; private final ResourceMappingContext context; private final boolean consultModels; private ISynchronizationScope scope; private boolean initialized; private ScopeManagerEventHandler handler; private final String name;
Convenience method for obtaining the set of resource mappings from all model providers that overlap with the given resources.
Params:
  • traversals – the resource traversals
  • context – the resource mapping context
  • monitor – a progress monitor
Throws:
Returns:the resource mappings
/** * Convenience method for obtaining the set of resource * mappings from all model providers that overlap * with the given resources. * @param traversals the resource traversals * @param context the resource mapping context * @param monitor a progress monitor * @return the resource mappings * @throws CoreException */
public static ResourceMapping[] getMappingsFromProviders(ResourceTraversal[] traversals, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { Set<ResourceMapping> result = new HashSet<>(); IModelProviderDescriptor[] descriptors = ModelProvider .getModelProviderDescriptors(); for (IModelProviderDescriptor descriptor : descriptors) { ResourceMapping[] mappings = getMappings(descriptor, traversals, context, monitor); result.addAll(Arrays.asList(mappings)); Policy.checkCanceled(monitor); } return result.toArray(new ResourceMapping[result.size()]); } private static ResourceMapping[] getMappings(IModelProviderDescriptor descriptor, ResourceTraversal[] traversals, ResourceMappingContext context, IProgressMonitor monitor) throws CoreException { ResourceTraversal[] matchingTraversals = descriptor.getMatchingTraversals( traversals); return descriptor.getModelProvider().getMappings(matchingTraversals, context, monitor); }
Create a scope manager that uses the given context to determine what resources should be included in the scope. If consultModels is true then the model providers will be queried in order to determine if additional mappings should be included in the scope
Params:
  • name – the name of the scope
  • inputMappings – the input mappings
  • resourceMappingContext – a resource mapping context
  • consultModels – whether model providers should be consulted
/** * Create a scope manager that uses the given context to * determine what resources should be included in the scope. * If <code>consultModels</code> is <code>true</code> then * the model providers will be queried in order to determine if * additional mappings should be included in the scope * @param name the name of the scope * @param inputMappings the input mappings * @param resourceMappingContext a resource mapping context * @param consultModels whether model providers should be consulted */
public SynchronizationScopeManager(String name, ResourceMapping[] inputMappings, ResourceMappingContext resourceMappingContext, boolean consultModels) { this.name = name; this.context = resourceMappingContext; this.consultModels = consultModels; scope = createScope(inputMappings); } @Override public boolean isInitialized() { return initialized; }
Return the scheduling rule that is used when initializing and refreshing the scope. By default, a rule that covers all projects for the input mappings of the scope is returned. Subclasses may override.
Returns:the scheduling rule that is used when initializing and refreshing the scope
/** * Return the scheduling rule that is used when initializing and refreshing * the scope. By default, a rule that covers all projects for the input mappings * of the scope is returned. Subclasses may override. * * @return the scheduling rule that is used when initializing and refreshing * the scope */
public ISchedulingRule getSchedulingRule() { Set<IProject> projects = new HashSet<>(); ResourceMapping[] mappings = scope.getInputMappings(); for (ResourceMapping mapping : mappings) { Object modelObject = mapping.getModelObject(); if (modelObject instanceof IResource) { IResource resource = (IResource) modelObject; if (resource.getType() == IResource.ROOT) // If the workspace root is one of the inputs, // then use the workspace root as the rule return ResourcesPlugin.getWorkspace().getRoot(); projects.add(resource.getProject()); } else { // If one of the inputs is not a resource, then use the // root as the rule since we don't know whether projects // can be added or removed return ResourcesPlugin.getWorkspace().getRoot(); } } return MultiRule.combine(projects.toArray(new IProject[projects.size()])); } @Override public void initialize( IProgressMonitor monitor) throws CoreException { ResourcesPlugin.getWorkspace().run((IWorkspaceRunnable) monitor1 -> internalPrepareContext(monitor1), getSchedulingRule(), IResource.NONE, monitor); } @Override public ResourceTraversal[] refresh(final ResourceMapping[] mappings, IProgressMonitor monitor) throws CoreException { // We need to lock the workspace when building the scope final ResourceTraversal[][] traversals = new ResourceTraversal[][] { new ResourceTraversal[0] }; IWorkspace workspace = ResourcesPlugin.getWorkspace(); workspace.run((IWorkspaceRunnable) monitor1 -> traversals[0] = internalRefreshScope(mappings, true, monitor1), getSchedulingRule(), IResource.NONE, monitor); return traversals[0]; } private void internalPrepareContext(IProgressMonitor monitor) throws CoreException { if (initialized) return; monitor.beginTask(null, IProgressMonitor.UNKNOWN); // Accumulate the initial set of mappings we need traversals for ((ResourceMappingScope)scope).reset(); ResourceMapping[] targetMappings = scope.getInputMappings(); ResourceTraversal[] newTraversals; boolean firstTime = true; boolean hasAdditionalResources = false; int count = 0; do { Policy.checkCanceled(monitor); newTraversals = addMappingsToScope(targetMappings, Policy.subMonitorFor(monitor, IProgressMonitor.UNKNOWN)); if (newTraversals.length > 0 && consultModels) { ResourceTraversal[] adjusted = adjustInputTraversals(newTraversals); targetMappings = getMappingsFromProviders(adjusted, context, Policy.subMonitorFor(monitor, IProgressMonitor.UNKNOWN)); if (firstTime) { firstTime = false; } else if (!hasAdditionalResources) { hasAdditionalResources = newTraversals.length != 0; } } } while (consultModels & newTraversals.length != 0 && count++ < MAX_ITERATION); setHasAdditionalMappings(scope, consultModels && internalHasAdditionalMappings()); setHasAdditionalResources(consultModels && hasAdditionalResources); monitor.done(); initialized = true; fireMappingsChangedEvent(scope.getMappings(), scope.getTraversals()); } private ResourceTraversal[] internalRefreshScope(ResourceMapping[] mappings, boolean checkForContraction, IProgressMonitor monitor) throws CoreException { monitor.beginTask(null, 100 * mappings.length + 100); ScopeChangeEvent change = new ScopeChangeEvent(scope); CompoundResourceTraversal refreshTraversals = new CompoundResourceTraversal(); CompoundResourceTraversal removedTraversals = new CompoundResourceTraversal(); for (ResourceMapping mapping : mappings) { ResourceTraversal[] previousTraversals = scope.getTraversals(mapping); ResourceTraversal[] mappingTraversals = mapping.getTraversals( context, Policy.subMonitorFor(monitor, 100)); refreshTraversals.addTraversals(mappingTraversals); ResourceTraversal[] uncovered = getUncoveredTraversals(mappingTraversals); if (checkForContraction && previousTraversals != null && previousTraversals.length > 0) { ResourceTraversal[] removed = getUncoveredTraversals(mappingTraversals, previousTraversals); removedTraversals.addTraversals(removed); } if (uncovered.length > 0) { change.setExpanded(true); ResourceTraversal[] result = performExpandScope(mapping, mappingTraversals, uncovered, monitor); refreshTraversals.addTraversals(result); } } if (checkForContraction && removedTraversals.getRoots().length > 0) { // The scope may have contracted. The only way to handle this is to recalculate from scratch // TODO: This may not be thread safe ((ResourceMappingScope)scope).reset(); internalRefreshScope(scope.getInputMappings(), false, monitor); change.setContracted(true); } if (change.shouldFireChange()) fireMappingsChangedEvent(change.getChangedMappings(), change.getChangedTraversals(refreshTraversals)); monitor.done(); return refreshTraversals.asTraversals(); } private ResourceTraversal[] getUncoveredTraversals( ResourceTraversal[] newTraversals, ResourceTraversal[] previousTraversals) { CompoundResourceTraversal t = new CompoundResourceTraversal(); t.addTraversals(newTraversals); return t.getUncoveredTraversals(previousTraversals); } private ResourceTraversal[] performExpandScope( ResourceMapping mapping, ResourceTraversal[] mappingTraversals, ResourceTraversal[] uncovered, IProgressMonitor monitor) throws CoreException { ResourceMapping ancestor = findAncestor(mapping); if (ancestor == null) { uncovered = addMappingToScope(mapping, mappingTraversals); addResourcesToScope(uncovered, monitor); return mappingTraversals; } else { ResourceTraversal[] ancestorTraversals = ancestor.getTraversals( context, Policy.subMonitorFor(monitor, 100)); uncovered = addMappingToScope(ancestor, ancestorTraversals); addResourcesToScope(uncovered, monitor); return ancestorTraversals; } } private ResourceMapping findAncestor(ResourceMapping mapping) { ResourceMapping[] mappings = scope.getMappings(mapping.getModelProviderId()); for (ResourceMapping m : mappings) { if (m.contains(mapping)) { return m; } } return null; } private ResourceTraversal[] getUncoveredTraversals(ResourceTraversal[] traversals) { return ((ResourceMappingScope)scope).getCompoundTraversal().getUncoveredTraversals(traversals); } private void addResourcesToScope(ResourceTraversal[] newTraversals, IProgressMonitor monitor) throws CoreException { if (!consultModels) return; ResourceMapping[] targetMappings; int count = 0; do { ResourceTraversal[] adjusted = adjustInputTraversals(newTraversals); targetMappings = getMappingsFromProviders(adjusted, context, Policy.subMonitorFor(monitor, IProgressMonitor.UNKNOWN)); newTraversals = addMappingsToScope(targetMappings, Policy.subMonitorFor(monitor, IProgressMonitor.UNKNOWN)); } while (newTraversals.length != 0 && count++ < MAX_ITERATION); if (!scope.hasAdditionalMappings()) { setHasAdditionalMappings(scope, internalHasAdditionalMappings()); } if (!scope.hasAdditonalResources()) { setHasAdditionalResources(true); } } /* * Fire a mappings changed event to any listeners on the scope. * The new mappings are obtained from the scope. * @param originalMappings the original mappings of the scope. */ private void fireMappingsChangedEvent(ResourceMapping[] newMappings, ResourceTraversal[] newTraversals) { ((ResourceMappingScope)scope).fireTraversalsChangedEvent(newTraversals, newMappings); }
Set whether the scope has additional mappings. This method is not intended to be overridden.
Params:
  • hasAdditionalMappings – a boolean indicating if the scope has additional mappings
/** * Set whether the scope has additional mappings. This method is not * intended to be overridden. * * @param hasAdditionalMappings a boolean indicating if the scope has * additional mappings */
protected final void setHasAdditionalMappings( ISynchronizationScope scope, boolean hasAdditionalMappings) { ((ResourceMappingScope)scope).setHasAdditionalMappings(hasAdditionalMappings); }
Set whether the scope has additional resources. This method is not intended to be overridden.
Params:
  • hasAdditionalResources – a boolean indicating if the scope has additional resources
/** * Set whether the scope has additional resources. This method is not * intended to be overridden. * * @param hasAdditionalResources a boolean indicating if the scope has * additional resources */
protected final void setHasAdditionalResources(boolean hasAdditionalResources) { ((ResourceMappingScope)scope).setHasAdditionalResources(hasAdditionalResources); }
Create the scope that will be populated and returned by the builder. This method is not intended to be overridden by clients.
Params:
  • inputMappings – the input mappings
Returns:a newly created scope that will be populated and returned by the builder
/** * Create the scope that will be populated and returned by the builder. This * method is not intended to be overridden by clients. * @param inputMappings the input mappings * @return a newly created scope that will be populated and returned by the * builder */
protected final ISynchronizationScope createScope( ResourceMapping[] inputMappings) { return new ResourceMappingScope(inputMappings, this); }
Adjust the given set of input resources to include any additional resources required by a particular repository provider for the current operation. By default the original set is returned but subclasses may override. Overriding methods should return a set of resources that include the original resource either explicitly or implicitly as a child of a returned resource.

Subclasses may override this method to include additional resources.

Params:
  • traversals – the input resource traversals
Returns:the input resource traversals adjusted to include any additional resources required for the current operation
/** * Adjust the given set of input resources to include any additional * resources required by a particular repository provider for the current * operation. By default the original set is returned but subclasses may * override. Overriding methods should return a set of resources that * include the original resource either explicitly or implicitly as a child * of a returned resource. * <p> * Subclasses may override this method to include additional resources. * * @param traversals the input resource traversals * @return the input resource traversals adjusted to include any additional resources * required for the current operation */
protected ResourceTraversal[] adjustInputTraversals(ResourceTraversal[] traversals) { return traversals; } private ResourceTraversal[] addMappingsToScope( ResourceMapping[] targetMappings, IProgressMonitor monitor) throws CoreException { CompoundResourceTraversal result = new CompoundResourceTraversal(); ResourceMappingContext context = this.context; for (ResourceMapping mapping : targetMappings) { if (scope.getTraversals(mapping) == null) { ResourceTraversal[] traversals = mapping.getTraversals(context, Policy.subMonitorFor(monitor, 100)); ResourceTraversal[] newOnes = addMappingToScope(mapping, traversals); result.addTraversals(newOnes); } Policy.checkCanceled(monitor); } return result.asTraversals(); }
Add the mapping and its calculated traversals to the scope. Return the resources that were not previously covered by the scope. This method is not intended to be subclassed by clients.
Params:
  • mapping – the resource mapping
  • traversals – the resource mapping's traversals
Returns:the resource traversals that were not previously covered by the scope
/** * Add the mapping and its calculated traversals to the scope. Return the * resources that were not previously covered by the scope. This method * is not intended to be subclassed by clients. * * @param mapping the resource mapping * @param traversals the resource mapping's traversals * @return the resource traversals that were not previously covered by the scope */
protected final ResourceTraversal[] addMappingToScope( ResourceMapping mapping, ResourceTraversal[] traversals) { return ((ResourceMappingScope)scope).addMapping(mapping, traversals); } private boolean internalHasAdditionalMappings() { ResourceMapping[] inputMappings = scope.getInputMappings(); ResourceMapping[] mappings = scope.getMappings(); if (inputMappings.length == mappings.length) { Set<ResourceMapping> testSet = new HashSet<>(); Collections.addAll(testSet, mappings); for (ResourceMapping mapping : inputMappings) { if (!testSet.contains(mapping)) { return true; } } return false; } return true; } public ResourceMappingContext getContext() { return context; } @Override public ISynchronizationScope getScope() { return scope; } @Override public void dispose() { if (handler != null) handler.shutdown(); }
Refresh the given mappings by recalculating the traversals for the mappings and adjusting the scope accordingly.
Params:
  • mappings – the mappings to be refreshed
/** * Refresh the given mappings by recalculating the traversals for the * mappings and adjusting the scope accordingly. * @param mappings the mappings to be refreshed */
public void refresh(ResourceMapping[] mappings) { getHandler().refresh(mappings); } private synchronized ScopeManagerEventHandler getHandler() { if (handler == null) handler = new ScopeManagerEventHandler(this); return handler; }
Returns the human readable name of this manager. The name is never null.
Returns:the name associated with this scope manager
/** * Returns the human readable name of this manager. The name is never * <code>null</code>. * @return the name associated with this scope manager */
public String getName() { return name; } }