| /******************************************************************************* |
| * 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.subscribers; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.mapping.ResourceMapping; |
| import org.eclipse.core.resources.mapping.ResourceTraversal; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.ISafeRunnable; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.team.core.ITeamStatus; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.core.TeamStatus; |
| import org.eclipse.team.core.diff.IDiff; |
| import org.eclipse.team.core.diff.IDiffVisitor; |
| import org.eclipse.team.core.diff.IThreeWayDiff; |
| import org.eclipse.team.core.synchronize.SyncInfo; |
| import org.eclipse.team.core.synchronize.SyncInfoSet; |
| import org.eclipse.team.core.variants.IResourceVariantComparator; |
| import org.eclipse.team.internal.core.Messages; |
| import org.eclipse.team.internal.core.Policy; |
| import org.eclipse.team.internal.core.TeamPlugin; |
| import org.eclipse.team.internal.core.mapping.SyncInfoToDiffConverter; |
| |
| /** |
| * A Subscriber provides synchronization between local resources and a |
| * remote location that is used to share those resources. |
| * <p> |
| * When queried for the <code>SyncInfo</code> corresponding to a local resource using |
| * <code>getSyncInfo(IResource)</code>, the subscriber should not contact the server. |
| * Server round trips should only occur within the <code>refresh</code> |
| * method of the subscriber. Consequently, |
| * the implementation of a subscriber must cache enough state information for a remote resource to calculate the |
| * synchronization state without contacting the server. During a refresh, the latest remote resource state |
| * information should be fetched and cached. For |
| * a subscriber that supports three-way compare, the refresh should also fetch the latest base state unless this is |
| * available by some other means (e.g. for some repository tools, the base state is persisted on disk with the |
| * local resources). |
| * </p> |
| * <p> |
| * After a refresh, the subscriber must notify any listeners of local resources whose corresponding remote resource |
| * or base resource changed. The subscriber does not need to notify listeners when the state changes due to a local |
| * modification since local changes are available through the <code>IResource</code> delta mechanism. However, |
| * the subscriber must |
| * cache enough information (e.g. the local timestamp of when the file was in-sync with its corresponding remote |
| * resource) |
| * to determine if the file represents an outgoing change so that <code>SyncInfo</code> obtained |
| * after a delta will indicate that the file has an outgoing change. The subscriber must also notify listeners |
| * when roots are added |
| * or removed. For example, a subscriber for a repository provider would fire a root added event when a project |
| * was shared |
| * with a repository. No event is required when a root is deleted as this is available through the |
| * <code>IResource</code> delta mechanism. It is up to clients to re-query the subscriber |
| * when the state of a resource changes locally by listening to <code>IResource</code> deltas. |
| * </p><p> |
| * The remote and base states can also include the state for resources that do not exist locally (i.e outgoing deletions |
| * or incoming additions). When queried for the members of a local resource, the subscriber should include any children |
| * for which a remote exists even if the local does not. |
| * </p> |
| * @since 3.0 |
| */ |
| abstract public class Subscriber { |
| |
| private List<ISubscriberChangeListener> listeners = new ArrayList<>(1); |
| |
| /** |
| * Return the name of this subscription, in a format that is |
| * suitable for display to an end user. |
| * |
| * @return String representing the name of this subscription. |
| */ |
| abstract public String getName(); |
| |
| /** |
| * Returns <code>true</code> if this resource is supervised by this |
| * subscriber. A supervised resource is one for which this subscriber |
| * maintains the synchronization state. Supervised resources are the only |
| * resources returned when <code>members(IResource)</code> was invoked with the parent |
| * of the resource. Returns <code>false</code> in all |
| * other cases. |
| * |
| * @param resource the resource being tested |
| * @return <code>true</code> if this resource is supervised, and <code>false</code> |
| * otherwise |
| * @throws TeamException |
| */ |
| abstract public boolean isSupervised(IResource resource) throws TeamException; |
| |
| /** |
| * Returns all non-transient member resources of the given resource. The |
| * result will include entries for resources that exist either in the |
| * workspace or are implicated in an incoming change. Returns an empty list |
| * if the given resource exists neither in the workspace nor in the |
| * corresponding subscriber location, or if the given resource is transient. |
| * <p> |
| * This is a fast operation; the repository is not contacted. |
| * </p> |
| * @param resource the resource |
| * @return a list of member resources |
| * @throws TeamException |
| */ |
| abstract public IResource[] members(IResource resource) throws TeamException; |
| |
| /** |
| * Returns the list of root resources this subscriber considers for |
| * synchronization. A client should call this method first then can safely |
| * call <code>members</code> to navigate the resources managed by this |
| * subscriber. |
| * |
| * @return a list of resources |
| */ |
| abstract public IResource[] roots(); |
| |
| /** |
| * Returns synchronization info for the given resource, or <code>null</code> |
| * if there is no synchronization info because the subscriber does not apply |
| * to this resource. |
| * <p> |
| * Note that sync info may be returned for non-existing or for resources |
| * which have no corresponding remote resource. |
| * </p> |
| * <p> |
| * This method will be quick. If synchronization calculation requires |
| * content from the server it must be cached when the subscriber is |
| * refreshed. A client should call refresh before calling this method to |
| * ensure that the latest information is available for computing the sync |
| * state. |
| * </p> |
| * <p> |
| * The sync-info node returned by this method does not fully describe |
| * all types of changes. A more descriptive change can be obtained from |
| * the {@link #getDiff(IResource) } method. |
| * |
| * @param resource the resource of interest |
| * @return sync info |
| * @throws TeamException |
| * @see #getDiff(IResource) |
| */ |
| abstract public SyncInfo getSyncInfo(IResource resource) throws TeamException; |
| |
| /** |
| * Returns the comparison criteria that will be used by the sync info |
| * created by this subscriber. |
| * |
| * @return the comparator to use when computing sync states for this |
| * subscriber. |
| */ |
| abstract public IResourceVariantComparator getResourceComparator(); |
| |
| /** |
| * Refreshes the resource hierarchy from the given resources and their |
| * children (to the specified depth) from the corresponding resources in the |
| * remote location. Resources are ignored in the following cases: |
| * <ul> |
| * <li>if they do not exist either in the workspace or in the corresponding |
| * remote location</li> |
| * <li>if the given resource is not supervised by this subscriber</li> |
| * <li>if the given resource is a closed project (they are ineligible for |
| * synchronization)</li> |
| * </ul> |
| * <p> |
| * Typical synchronization operations use the statuses computed by this |
| * method as the basis for determining what to do. It is possible for the |
| * actual sync status of the resource to have changed since the current |
| * local sync status was refreshed. Operations typically skip resources with |
| * stale sync information. The chances of stale information being used can |
| * be reduced by running this method (where feasible) before doing other |
| * operations. Note that this will of course affect performance. |
| * </p> |
| * <p> |
| * The depth parameter controls whether refreshing is performed on just the |
| * given resource (depth= <code>DEPTH_ZERO</code>), the resource and its |
| * children (depth= <code>DEPTH_ONE</code>), or recursively to the |
| * resource and all its descendents (depth= <code>DEPTH_INFINITE</code>). |
| * Use depth <code>DEPTH_ONE</code>, rather than depth |
| * <code>DEPTH_ZERO</code>, to ensure that new members of a project or |
| * folder are detected. |
| * </p> |
| * <p> |
| * This method might change resources; any changes will be reported in a |
| * subsequent subscriber resource change event indicating changes to server |
| * sync status. |
| * </p> |
| * <p> |
| * This method contacts the server and is therefore long-running; progress |
| * and cancellation are provided by the given progress monitor. |
| * </p> |
| * @param resources the resources |
| * @param depth valid values are <code>DEPTH_ZERO</code>, |
| * <code>DEPTH_ONE</code>, or <code>DEPTH_INFINITE</code> |
| * @param monitor progress monitor, or <code>null</code> if progress |
| * reporting and cancellation are not desired |
| * @exception TeamException if this method fails. Reasons include: |
| * <ul> |
| * <li>The server could not be contacted.</li> |
| * </ul> |
| */ |
| abstract public void refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException; |
| |
| /** |
| * Adds a listener to this team subscriber. Has no effect if an identical |
| * listener is already registered. |
| * <p> |
| * Team resource change listeners are informed about state changes that |
| * affect the resources supervised by this subscriber. |
| * </p> |
| * @param listener a team resource change listener |
| */ |
| public void addListener(ISubscriberChangeListener listener) { |
| synchronized (listeners) { |
| if (!listeners.contains(listener)) { |
| listeners.add(listener); |
| } |
| } |
| } |
| |
| /** |
| * Removes a listener previously registered with this team subscriber. Has |
| * no effect if an identical listener is not registered. |
| * |
| * @param listener a team resource change listener |
| */ |
| public void removeListener(ISubscriberChangeListener listener) { |
| synchronized (listeners) { |
| listeners.remove(listener); |
| } |
| } |
| |
| /** |
| * Adds all out-of-sync resources (<code>getKind() != SyncInfo.IN_SYNC</code>) that occur |
| * under the given resources to the specified depth. The purpose of this |
| * method is to provide subscribers a means of optimizing the determination |
| * of all out-of-sync descendants of a set of resources. |
| * <p> |
| * If any of the directly provided resources are not supervised by the subscriber, then |
| * they should be removed from the set. |
| * If errors occur while determining the sync info for the resources, they should |
| * be added to the set using <code>SyncInfoSet.addError</code>. |
| * </p> |
| * @param resources the root of the resource subtrees from which out-of-sync sync info should be collected |
| * @param depth the depth to which sync info should be collected |
| * (one of <code>IResource.DEPTH_ZERO</code>, |
| * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>) |
| * @param set the sync info set to which out-of-sync resources should be added (or removed). Any errors |
| * should be added to the set as well. |
| * @param monitor a progress monitor |
| */ |
| public void collectOutOfSync(IResource[] resources, int depth, SyncInfoSet set, IProgressMonitor monitor) { |
| try { |
| monitor.beginTask(null, 100 * resources.length); |
| for (IResource resource : resources) { |
| IProgressMonitor subMonitor = Policy.subMonitorFor(monitor, 100); |
| subMonitor.beginTask(null, IProgressMonitor.UNKNOWN); |
| collect(resource, depth, set, subMonitor); |
| subMonitor.done(); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Fires a team resource change event to all registered listeners. Only |
| * listeners registered at the time this method is called are notified. |
| * Listener notification makes use of an <code>ISafeRunnable</code> to ensure that |
| * client exceptions do not affect the notification to other clients. |
| */ |
| protected void fireTeamResourceChange(final ISubscriberChangeEvent[] deltas) { |
| ISubscriberChangeListener[] allListeners; |
| // Copy the listener list so we're not calling client code while synchronized |
| synchronized (listeners) { |
| allListeners = listeners.toArray(new ISubscriberChangeListener[listeners.size()]); |
| } |
| // Notify the listeners safely so all will receive notification |
| for (ISubscriberChangeListener listener : allListeners) { |
| SafeRunner.run(new ISafeRunnable() { |
| @Override |
| public void handleException(Throwable exception) { |
| // don't log the exception....it is already being logged in |
| // Platform#run |
| } |
| @Override |
| public void run() throws Exception { |
| listener.subscriberResourceChanged(deltas); |
| } |
| }); |
| } |
| } |
| |
| /* |
| * Collect the calculated synchronization information for the given resource at the given depth. The |
| * results are added to the provided list. |
| */ |
| private void collect( |
| IResource resource, |
| int depth, |
| SyncInfoSet set, |
| IProgressMonitor monitor) { |
| |
| Policy.checkCanceled(monitor); |
| |
| if (resource.getType() != IResource.FILE |
| && depth != IResource.DEPTH_ZERO) { |
| try { |
| IResource[] members = members(resource); |
| for (IResource member : members) { |
| collect(member, depth == IResource.DEPTH_INFINITE |
| ? IResource.DEPTH_INFINITE |
| : IResource.DEPTH_ZERO, set, monitor); |
| } |
| } catch (TeamException e) { |
| set.addError(new TeamStatus(IStatus.ERROR, TeamPlugin.ID, ITeamStatus.SYNC_INFO_SET_ERROR, NLS.bind(Messages.SubscriberEventHandler_8, new String[] { resource.getFullPath().toString(), e.getMessage() }), e, resource)); |
| } |
| } |
| |
| monitor.subTask(NLS.bind(Messages.SubscriberEventHandler_2, new String[] { resource.getFullPath().toString() })); |
| try { |
| SyncInfo info = getSyncInfo(resource); |
| if (info == null || info.getKind() == SyncInfo.IN_SYNC) { |
| // Resource is no longer under the subscriber control. |
| // This can occur for the resources past as arguments to collectOutOfSync |
| set.remove(resource); |
| } else { |
| set.add(info); |
| } |
| } catch (TeamException e) { |
| set.addError(new TeamStatus( |
| IStatus.ERROR, TeamPlugin.ID, ITeamStatus.RESOURCE_SYNC_INFO_ERROR, |
| NLS.bind(Messages.SubscriberEventHandler_9, new String[] { resource.getFullPath().toString(), e.getMessage() }), |
| e, resource)); |
| } |
| // Tick the monitor to give the owner a chance to do something |
| monitor.worked(1); |
| } |
| |
| /** |
| * Returns synchronization info, in the form of an {@link IDiff} for the |
| * given resource, or <code>null</code> if there is no synchronization |
| * info because the subscriber does not apply to this resource or the resource |
| * is in-sync. |
| * <p> |
| * Note that a diff may be returned for non-existing or for resources |
| * which have no corresponding remote resource. |
| * </p> |
| * <p> |
| * This method will be quick. If synchronization calculation requires |
| * content from the server it must be cached when the subscriber is |
| * refreshed. A client should call refresh before calling this method to |
| * ensure that the latest information is available for computing the diff. |
| * </p> |
| * <p> |
| * The diff node returned by this method describes the changes associated |
| * with the given resource in more detail than the sync-info returned |
| * by calling {@link #getSyncInfo(IResource) }. |
| * |
| * @param resource the resource of interest |
| * @return the diff for the resource or <code>null</code> |
| * @throws CoreException |
| * @throws TeamException if errors occur |
| * @since 3.2 |
| */ |
| public IDiff getDiff(IResource resource) throws CoreException { |
| SyncInfo info = getSyncInfo(resource); |
| if (info == null || info.getKind() == SyncInfo.IN_SYNC) |
| return null; |
| return SyncInfoToDiffConverter.getDefault().getDeltaFor(info); |
| } |
| |
| /** |
| * Visit any out-of-sync resources covered by the given traversals. Any resources |
| * covered by the traversals are ignored in the following cases: |
| * <ul> |
| * <li>if they do not exist either in the workspace or in the corresponding |
| * remote location</li> |
| * <li>if the given resource is not supervised by this subscriber</li> |
| * <li>if the given resource is a closed project (they are ineligible for |
| * synchronization)</li> |
| * </ul> |
| * @param traversals the traversals to be visited |
| * @param visitor the visitor |
| * @throws CoreException |
| * @throws TeamException if errors occur |
| * @since 3.2 |
| */ |
| public void accept(ResourceTraversal[] traversals, IDiffVisitor visitor) throws CoreException { |
| for (ResourceTraversal traversal : traversals) { |
| accept(traversal.getResources(), traversal.getDepth(), visitor); |
| } |
| } |
| |
| /** |
| * Visit any out-of-sync resources in the given resources visited to the |
| * given depth. Resources are ignored in the following cases: |
| * <ul> |
| * <li>if they do not exist either in the workspace or in the corresponding |
| * remote location</li> |
| * <li>if the given resource is not supervised by this subscriber</li> |
| * <li>if the given resource is a closed project (they are ineligible for |
| * synchronization)</li> |
| * </ul> |
| * |
| * @param resources the root of the resource subtrees from which out-of-sync |
| * sync info should be visited |
| * @param depth the depth to which sync info should be collected (one of |
| * <code>IResource.DEPTH_ZERO</code>, |
| * <code>IResource.DEPTH_ONE</code>, or |
| * <code>IResource.DEPTH_INFINITE</code>) |
| * @param visitor the visitor |
| * @throws CoreException if errors occur |
| * @since 3.2 |
| */ |
| public void accept(IResource[] resources, int depth, IDiffVisitor visitor) throws CoreException { |
| for (IResource resource : resources) { |
| accept(resource, depth, visitor); |
| } |
| } |
| |
| private void accept(IResource resource, int depth, IDiffVisitor visitor) throws CoreException { |
| IDiff node = getDiff(resource); |
| if (node != null && node.getKind() != IDiff.NO_CHANGE) { |
| if (!visitor.visit(node)) |
| return; |
| } |
| if (depth != IResource.DEPTH_ZERO) { |
| IResource[] members = members(resource); |
| int newDepth = depth == IResource.DEPTH_INFINITE ? IResource.DEPTH_INFINITE : IResource.DEPTH_ZERO; |
| for (IResource member : members) { |
| accept(member, newDepth, visitor); |
| } |
| } |
| } |
| |
| /** |
| * Refresh the subscriber for the given traversals. By default this method calls |
| * {@link #refresh(IResource[], int, IProgressMonitor) } for each traversal. Any resources |
| * covered by the traversals are ignored in the following cases: |
| * <ul> |
| * <li>if they do not exist either in the workspace or in the corresponding |
| * remote location</li> |
| * <li>if the given resource is not supervised by this subscriber</li> |
| * <li>if the given resource is a closed project (they are ineligible for |
| * synchronization)</li> |
| * </ul> |
| * <p> |
| * Subclasses may override. |
| * @param traversals the traversals to be refreshed |
| * @param monitor a progress monitor |
| * @throws TeamException if errors occur |
| * @since 3.2 |
| */ |
| public void refresh(ResourceTraversal[] traversals, IProgressMonitor monitor) throws TeamException { |
| monitor.beginTask(null, 100 * traversals.length); |
| for (ResourceTraversal traversal : traversals) { |
| refresh(traversal.getResources(), traversal.getDepth(), Policy.subMonitorFor(monitor, 100)); |
| } |
| monitor.done(); |
| } |
| |
| /** |
| * Return the synchronization state of the given resource mapping. |
| * Only return the portion of the synchronization state that matches |
| * the provided <code>stateMask</code>. The synchronization state flags that are |
| * guaranteed to be interpreted by this method are: |
| * <ul> |
| * <li>The kind flags {@link IDiff#ADD}, {@link IDiff#REMOVE} and {@link IDiff#CHANGE}. |
| * If none of these flags are included then all are assumed. |
| * <li>The direction flags {@link IThreeWayDiff#INCOMING} and {@link IThreeWayDiff#OUTGOING} if the |
| * subscriber is a three-way subscriber. If neither are provided, both are assumed. |
| * </ul> |
| * Other flags can be included and may or may not be interpreted by the subscriber. |
| * <p> |
| * An element will only include {@link IDiff#ADD} in the returned state if all resources covered |
| * by the traversals mappings are added. Similarly, {@link IDiff#REMOVE} will only be included |
| * if all the resources covered by the traversals are deleted. Otherwise {@link IDiff#CHANGE} |
| * will be returned. |
| * |
| * @param mapping the resource mapping whose synchronization state is to be determined |
| * @param stateMask the mask that identifies the state flags of interested |
| * @param monitor a progress monitor |
| * @return the synchronization state of the given resource mapping |
| * @throws CoreException |
| * @since 3.2 |
| * @see IDiff |
| * @see IThreeWayDiff |
| */ |
| public int getState(ResourceMapping mapping, int stateMask, IProgressMonitor monitor) throws CoreException { |
| ResourceTraversal[] traversals = mapping.getTraversals(new SubscriberResourceMappingContext(this, true), monitor); |
| final int[] direction = new int[] { 0 }; |
| final int[] kind = new int[] { 0 }; |
| accept(traversals, new IDiffVisitor() { |
| @Override |
| public boolean visit(IDiff diff) { |
| if (diff instanceof IThreeWayDiff) { |
| IThreeWayDiff twd = (IThreeWayDiff) diff; |
| direction[0] |= twd.getDirection(); |
| } |
| // If the traversals contain a combination of kinds, return a CHANGE |
| int diffKind = diff.getKind(); |
| if (kind[0] == 0) |
| kind[0] = diffKind; |
| if (kind[0] != diffKind) { |
| kind[0] = IDiff.CHANGE; |
| } |
| // Only need to visit the children of a change |
| return diffKind == IDiff.CHANGE; |
| } |
| }); |
| return (direction[0] | kind[0]) & stateMask; |
| } |
| } |