| /******************************************************************************* |
| * Copyright (c) 2000, 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * 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.ResourceTraversal; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.team.core.*; |
| import org.eclipse.team.core.diff.IDiffNode; |
| import org.eclipse.team.core.diff.IDiffVisitor; |
| 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.*; |
| 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 and 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 IResource 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 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. |
| * |
| * @return <code>true</code> if this resource is supervised, and <code>false</code> |
| * otherwise |
| */ |
| 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 |
| */ |
| 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 |
| * @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> |
| * <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 affect 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 (getKind() != IN_SYNC) 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 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>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 (int i = 0; i < resources.length; i++) { |
| IResource resource = resources[i]; |
| 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 ISafeRunnable to ensure that |
| * client exceptions do not effect 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 = (ISubscriberChangeListener[]) listeners.toArray(new ISubscriberChangeListener[listeners.size()]); |
| } |
| // Notify the listeners safely so all will receive notification |
| for (int i = 0; i < allListeners.length; i++) { |
| final ISubscriberChangeListener listener = allListeners[i]; |
| Platform.run(new ISafeRunnable() { |
| public void handleException(Throwable exception) { |
| // don't log the exception....it is already being logged in |
| // Platform#run |
| } |
| 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 (int i = 0; i < members.length; i++) { |
| collect( |
| members[i], |
| 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 IDiffNode} 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 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 TeamException if errors occur |
| * @since 3.2 |
| */ |
| public IDiffNode getDiff(IResource resource) throws CoreException { |
| SyncInfo info = getSyncInfo(resource); |
| if (info == null) |
| return null; |
| return SyncInfoToDiffConverter.getDeltaFor(info); |
| } |
| |
| /** |
| * Visit any out-of-sync resources covered by the given traversals. |
| * @param traversals the traversals to be visited |
| * @param visitor the visitor |
| * @throws TeamException if errors occur |
| * @since 3.2 |
| */ |
| public void accept(ResourceTraversal[] traversals, IDiffVisitor visitor) throws CoreException { |
| for (int i = 0; i < traversals.length; i++) { |
| ResourceTraversal traversal = traversals[i]; |
| accept(traversal.getResources(), traversal.getDepth(), visitor); |
| } |
| } |
| |
| /** |
| * Visit any out-of-sync resources in the given resources visited to the |
| * given depth. |
| * |
| * @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 TeamException if errors occur |
| * @since 3.2 |
| */ |
| public void accept(IResource[] resources, int depth, IDiffVisitor visitor) throws CoreException { |
| for (int i = 0; i < resources.length; i++) { |
| IResource resource = resources[i]; |
| accept(resource, depth, visitor); |
| } |
| } |
| |
| private void accept(IResource resource, int depth, IDiffVisitor visitor) throws CoreException { |
| IDiffNode node = getDiff(resource); |
| if (node != null && node.getKind() != IDiffNode.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 (int i = 0; i < members.length; i++) { |
| IResource member = members[i]; |
| accept(member, newDepth, visitor); |
| } |
| } |
| } |
| } |