KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > core > subscribers > Subscriber


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.team.core.subscribers;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.List JavaDoc;
15
16 import org.eclipse.core.resources.IResource;
17 import org.eclipse.core.resources.mapping.ResourceMapping;
18 import org.eclipse.core.resources.mapping.ResourceTraversal;
19 import org.eclipse.core.runtime.*;
20 import org.eclipse.osgi.util.NLS;
21 import org.eclipse.team.core.*;
22 import org.eclipse.team.core.diff.*;
23 import org.eclipse.team.core.synchronize.SyncInfo;
24 import org.eclipse.team.core.synchronize.SyncInfoSet;
25 import org.eclipse.team.core.variants.IResourceVariantComparator;
26 import org.eclipse.team.internal.core.*;
27 import org.eclipse.team.internal.core.mapping.SyncInfoToDiffConverter;
28
29 /**
30  * A Subscriber provides synchronization between local resources and a
31  * remote location that is used to share those resources.
32  * <p>
33  * When queried for the <code>SyncInfo</code> corresponding to a local resource using
34  * <code>getSyncInfo(IResource)</code>, the subscriber should not contact the server.
35  * Server round trips should only occur within the <code>refresh<code>
36  * method of the subscriber. Consequently,
37  * the implementation of a subscriber must cache enough state information for a remote resource to calculate the
38  * synchronization state without contacting the server. During a refresh, the latest remote resource state
39  * information should be fetched and cached. For
40  * a subscriber that supports three-way compare, the refresh should also fetch the latest base state unless this is
41  * available by some other means (e.g. for some repository tools, the base state is persisted on disk with the
42  * local resources).
43  * </p>
44  * <p>
45  * After a refresh, the subscriber must notify any listeners of local resources whose corresponding remote resource
46  * or base resource changed. The subscriber does not need to notify listeners when the state changes due to a local
47  * modification since local changes are available through the <code>IResource</code> delta mechanism. However,
48  * the subscriber must
49  * cache enough information (e.g. the local timestamp of when the file was in-sync with its corresponding remote
50  * resource)
51  * to determine if the file represents an outgoing change so that <code>SyncInfo</code> obtained
52  * after a delta will indicate that the file has an outgoing change. The subscriber must also notify listeners
53  * when roots and added
54  * or removed. For example, a subscriber for a repository provider would fire a root added event when a project
55  * was shared
56  * with a repository. No event is required when a root is deleted as this is available through the
57  * <code>IResource</code> delta mechanism. It is up to clients to re-query the subscriber
58  * when the state of a resource changes locally by listening to IResource deltas.
59  * </p><p>
60  * The remote and base states can also include the state for resources that do not exist locally (i.e outgoing deletions
61  * or incoming additions). When queried for the members of a local resource, the subscriber should include any children
62  * for which a remote exists even if the local does not.
63  * </p>
64  * @since 3.0
65  */

66 abstract public class Subscriber {
67
68     private List JavaDoc listeners = new ArrayList JavaDoc(1);
69
70     /**
71      * Return the name of this subscription, in a format that is
72      * suitable for display to an end user.
73      *
74      * @return String representing the name of this subscription.
75      */

76     abstract public String JavaDoc getName();
77
78     /**
79      * Returns <code>true</code> if this resource is supervised by this
80      * subscriber. A supervised resource is one for which this subscriber
81      * maintains the synchronization state. Supervised resources are the only
82      * resources returned when <code>members(IResource)</code> was invoked with the parent
83      * of the resource. Returns <code>false</code> in all
84      * other cases.
85      *
86      * @param resource the resource being tested
87      * @return <code>true</code> if this resource is supervised, and <code>false</code>
88      * otherwise
89      * @throws TeamException
90      */

91     abstract public boolean isSupervised(IResource resource) throws TeamException;
92
93     /**
94      * Returns all non-transient member resources of the given resource. The
95      * result will include entries for resources that exist either in the
96      * workspace or are implicated in an incoming change. Returns an empty list
97      * if the given resource exists neither in the workspace nor in the
98      * corresponding subscriber location, or if the given resource is transient.
99      * <p>
100      * This is a fast operation; the repository is not contacted.
101      * </p>
102      * @param resource the resource
103      * @return a list of member resources
104      * @throws TeamException
105      */

106     abstract public IResource[] members(IResource resource) throws TeamException;
107
108     /**
109      * Returns the list of root resources this subscriber considers for
110      * synchronization. A client should call this method first then can safely
111      * call <code>members</code> to navigate the resources managed by this
112      * subscriber.
113      *
114      * @return a list of resources
115      */

116     abstract public IResource[] roots();
117
118     /**
119      * Returns synchronization info for the given resource, or <code>null</code>
120      * if there is no synchronization info because the subscriber does not apply
121      * to this resource.
122      * <p>
123      * Note that sync info may be returned for non-existing or for resources
124      * which have no corresponding remote resource.
125      * </p>
126      * <p>
127      * This method will be quick. If synchronization calculation requires
128      * content from the server it must be cached when the subscriber is
129      * refreshed. A client should call refresh before calling this method to
130      * ensure that the latest information is available for computing the sync
131      * state.
132      * </p>
133      * <p>
134      * The sync-info node returned by this method does not fully describe
135      * all types of changes. A more descriptive change can be obtained from
136      * the {@link #getDiff(IResource) } method.
137      *
138      * @param resource the resource of interest
139      * @return sync info
140      * @throws TeamException
141      * @see #getDiff(IResource)
142      */

143     abstract public SyncInfo getSyncInfo(IResource resource) throws TeamException;
144     
145     /**
146      * Returns the comparison criteria that will be used by the sync info
147      * created by this subscriber.
148      *
149      * @return the comparator to use when computing sync states for this
150      * subscriber.
151      */

152     abstract public IResourceVariantComparator getResourceComparator();
153     
154     /**
155      * Refreshes the resource hierarchy from the given resources and their
156      * children (to the specified depth) from the corresponding resources in the
157      * remote location. Resources are ignored in the following cases:
158      * <ul>
159      * <li>if they do not exist either in the workspace or in the corresponding
160      * remote location</li>
161      * <li>if the given resource is not supervised by this subscriber</li>
162      * <li>if the given resource is a closed project (they are ineligible for
163      * synchronization)</li>
164      * <p>
165      * Typical synchronization operations use the statuses computed by this
166      * method as the basis for determining what to do. It is possible for the
167      * actual sync status of the resource to have changed since the current
168      * local sync status was refreshed. Operations typically skip resources with
169      * stale sync information. The chances of stale information being used can
170      * be reduced by running this method (where feasible) before doing other
171      * operations. Note that this will of course affect performance.
172      * </p>
173      * <p>
174      * The depth parameter controls whether refreshing is performed on just the
175      * given resource (depth= <code>DEPTH_ZERO</code>), the resource and its
176      * children (depth= <code>DEPTH_ONE</code>), or recursively to the
177      * resource and all its descendents (depth= <code>DEPTH_INFINITE</code>).
178      * Use depth <code>DEPTH_ONE</code>, rather than depth
179      * <code>DEPTH_ZERO</code>, to ensure that new members of a project or
180      * folder are detected.
181      * </p>
182      * <p>
183      * This method might change resources; any changes will be reported in a
184      * subsequent subscriber resource change event indicating changes to server
185      * sync status.
186      * </p>
187      * <p>
188      * This method contacts the server and is therefore long-running; progress
189      * and cancellation are provided by the given progress monitor.
190      * </p>
191      * @param resources the resources
192      * @param depth valid values are <code>DEPTH_ZERO</code>,
193      * <code>DEPTH_ONE</code>, or <code>DEPTH_INFINITE</code>
194      * @param monitor progress monitor, or <code>null</code> if progress
195      * reporting and cancellation are not desired
196      * @exception TeamException if this method fails. Reasons include:
197      * <ul>
198      * <li>The server could not be contacted.</li>
199      * </ul>
200      */

201     abstract public void refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException;
202
203     /**
204      * Adds a listener to this team subscriber. Has no effect if an identical
205      * listener is already registered.
206      * <p>
207      * Team resource change listeners are informed about state changes that
208      * affect the resources supervised by this subscriber.
209      * </p>
210      * @param listener a team resource change listener
211      */

212     public void addListener(ISubscriberChangeListener listener) {
213         synchronized (listeners) {
214             if (!listeners.contains(listener)) {
215                 listeners.add(listener);
216             }
217         }
218     }
219
220     /**
221      * Removes a listener previously registered with this team subscriber. Has
222      * no affect if an identical listener is not registered.
223      *
224      * @param listener a team resource change listener
225      */

226     public void removeListener(ISubscriberChangeListener listener) {
227         synchronized (listeners) {
228             listeners.remove(listener);
229         }
230     }
231     
232     /**
233      * Adds all out-of-sync resources (getKind() != IN_SYNC) that occur
234      * under the given resources to the specified depth. The purpose of this
235      * method is to provide subscribers a means of optimizing the determination
236      * of all out-of-sync out-of-sync descendants of a set of resources.
237      * <p>
238      * If any of the directly provided resources are not supervised by the subscriber, then
239      * they should be removed from the set.
240      * If errors occur while determining the sync info for the resources, they should
241      * be added to the set using <code>addError</code>.
242      * </p>
243      * @param resources the root of the resource subtrees from which out-of-sync sync info should be collected
244      * @param depth the depth to which sync info should be collected
245      * (one of <code>IResource.DEPTH_ZERO</code>,
246      * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
247      * @param set the sync info set to which out-of-sync resources should be added (or removed). Any errors
248      * should be added to the set as well.
249      * @param monitor a progress monitor
250      */

251     public void collectOutOfSync(IResource[] resources, int depth, SyncInfoSet set, IProgressMonitor monitor) {
252         try {
253             monitor.beginTask(null, 100 * resources.length);
254             for (int i = 0; i < resources.length; i++) {
255                 IResource resource = resources[i];
256                 IProgressMonitor subMonitor = Policy.subMonitorFor(monitor, 100);
257                 subMonitor.beginTask(null, IProgressMonitor.UNKNOWN);
258                 collect(resource, depth, set, subMonitor);
259                 subMonitor.done();
260             }
261         } finally {
262             monitor.done();
263         }
264     }
265     
266     /**
267      * Fires a team resource change event to all registered listeners Only
268      * listeners registered at the time this method is called are notified.
269      * Listener notification makes use of an ISafeRunnable to ensure that
270      * client exceptions do not effect the notification to other clients.
271      */

272     protected void fireTeamResourceChange(final ISubscriberChangeEvent[] deltas) {
273         ISubscriberChangeListener[] allListeners;
274         // Copy the listener list so we're not calling client code while synchronized
275
synchronized (listeners) {
276             allListeners = (ISubscriberChangeListener[]) listeners.toArray(new ISubscriberChangeListener[listeners.size()]);
277         }
278         // Notify the listeners safely so all will receive notification
279
for (int i = 0; i < allListeners.length; i++) {
280             final ISubscriberChangeListener listener = allListeners[i];
281             SafeRunner.run(new ISafeRunnable() {
282                 public void handleException(Throwable JavaDoc exception) {
283                     // don't log the exception....it is already being logged in
284
// Platform#run
285
}
286                 public void run() throws Exception JavaDoc {
287                     listener.subscriberResourceChanged(deltas);
288                 }
289             });
290         }
291     }
292     
293     /*
294      * Collect the calculated synchronization information for the given resource at the given depth. The
295      * results are added to the provided list.
296      */

297     private void collect(
298         IResource resource,
299         int depth,
300         SyncInfoSet set,
301         IProgressMonitor monitor) {
302         
303         Policy.checkCanceled(monitor);
304         
305         if (resource.getType() != IResource.FILE
306             && depth != IResource.DEPTH_ZERO) {
307             try {
308                 IResource[] members = members(resource);
309                 for (int i = 0; i < members.length; i++) {
310                     collect(
311                         members[i],
312                         depth == IResource.DEPTH_INFINITE
313                             ? IResource.DEPTH_INFINITE
314                             : IResource.DEPTH_ZERO,
315                         set,
316                         monitor);
317                 }
318             } catch (TeamException e) {
319                 set.addError(new TeamStatus(IStatus.ERROR, TeamPlugin.ID, ITeamStatus.SYNC_INFO_SET_ERROR, NLS.bind(Messages.SubscriberEventHandler_8, new String JavaDoc[] { resource.getFullPath().toString(), e.getMessage() }), e, resource));
320             }
321         }
322
323         monitor.subTask(NLS.bind(Messages.SubscriberEventHandler_2, new String JavaDoc[] { resource.getFullPath().toString() }));
324         try {
325             SyncInfo info = getSyncInfo(resource);
326             if (info == null || info.getKind() == SyncInfo.IN_SYNC) {
327                 // Resource is no longer under the subscriber control.
328
// This can occur for the resources past as arguments to collectOutOfSync
329
set.remove(resource);
330             } else {
331                 set.add(info);
332             }
333         } catch (TeamException e) {
334             set.addError(new TeamStatus(
335                     IStatus.ERROR, TeamPlugin.ID, ITeamStatus.RESOURCE_SYNC_INFO_ERROR,
336                     NLS.bind(Messages.SubscriberEventHandler_9, new String JavaDoc[] { resource.getFullPath().toString(), e.getMessage() }),
337                     e, resource));
338         }
339         // Tick the monitor to give the owner a chance to do something
340
monitor.worked(1);
341     }
342     
343     /**
344      * Returns synchronization info, in the form of an {@link IDiff} for the
345      * given resource, or <code>null</code> if there is no synchronization
346      * info because the subscriber does not apply to this resource or the resource
347      * is in-sync.
348      * <p>
349      * Note that a diff may be returned for non-existing or for resources
350      * which have no corresponding remote resource.
351      * </p>
352      * <p>
353      * This method will be quick. If synchronization calculation requires
354      * content from the server it must be cached when the subscriber is
355      * refreshed. A client should call refresh before calling this method to
356      * ensure that the latest information is available for computing the diff.
357      * </p>
358      * <p>
359      * The diff node returned by this method describes the changes associated
360      * with the given resource in more detail than the sync-info returned
361      * by calling {@link #getSyncInfo(IResource) }.
362      *
363      * @param resource the resource of interest
364      * @return the diff for the resource or <code>null</code>
365      * @throws CoreException
366      * @throws TeamException if errors occur
367      * @since 3.2
368      */

369     public IDiff getDiff(IResource resource) throws CoreException {
370         SyncInfo info = getSyncInfo(resource);
371         if (info == null || info.getKind() == SyncInfo.IN_SYNC)
372             return null;
373         return SyncInfoToDiffConverter.getDefault().getDeltaFor(info);
374     }
375     
376     /**
377      * Visit any out-of-sync resources covered by the given traversals. Any resources
378      * covered by the traversals are ignored in the following cases:
379      * <ul>
380      * <li>if they do not exist either in the workspace or in the corresponding
381      * remote location</li>
382      * <li>if the given resource is not supervised by this subscriber</li>
383      * <li>if the given resource is a closed project (they are ineligible for
384      * synchronization)</li>
385      * </ul>
386      * @param traversals the traversals to be visited
387      * @param visitor the visitor
388      * @throws CoreException
389      * @throws TeamException if errors occur
390      * @since 3.2
391      */

392     public void accept(ResourceTraversal[] traversals, IDiffVisitor visitor) throws CoreException {
393         for (int i = 0; i < traversals.length; i++) {
394             ResourceTraversal traversal = traversals[i];
395             accept(traversal.getResources(), traversal.getDepth(), visitor);
396         }
397     }
398     
399     /**
400      * Visit any out-of-sync resources in the given resources visited to the
401      * given depth. Resources are ignored in the following cases:
402      * <ul>
403      * <li>if they do not exist either in the workspace or in the corresponding
404      * remote location</li>
405      * <li>if the given resource is not supervised by this subscriber</li>
406      * <li>if the given resource is a closed project (they are ineligible for
407      * synchronization)</li>
408      * </ul>
409      *
410      * @param resources the root of the resource subtrees from which out-of-sync
411      * sync info should be visited
412      * @param depth the depth to which sync info should be collected (one of
413      * <code>IResource.DEPTH_ZERO</code>,
414      * <code>IResource.DEPTH_ONE</code>, or
415      * <code>IResource.DEPTH_INFINITE</code>)
416      * @param visitor the visitor
417      * @throws CoreException if errors occur
418      * @since 3.2
419      */

420     public void accept(IResource[] resources, int depth, IDiffVisitor visitor) throws CoreException {
421         for (int i = 0; i < resources.length; i++) {
422             IResource resource = resources[i];
423             accept(resource, depth, visitor);
424         }
425     }
426
427     private void accept(IResource resource, int depth, IDiffVisitor visitor) throws CoreException {
428         IDiff node = getDiff(resource);
429         if (node != null && node.getKind() != IDiff.NO_CHANGE) {
430             if (!visitor.visit(node))
431                 return;
432         }
433         if (depth != IResource.DEPTH_ZERO) {
434             IResource[] members = members(resource);
435             int newDepth = depth == IResource.DEPTH_INFINITE ? IResource.DEPTH_INFINITE : IResource.DEPTH_ZERO;
436             for (int i = 0; i < members.length; i++) {
437                 IResource member = members[i];
438                 accept(member, newDepth, visitor);
439             }
440         }
441     }
442
443     /**
444      * Refresh the subscriber for the given traversals. By default this method calls
445      * {@link #refresh(IResource[], int, IProgressMonitor) } for each traversal. Any resources
446      * covered by the traversals are ignored in the following cases:
447      * <ul>
448      * <li>if they do not exist either in the workspace or in the corresponding
449      * remote location</li>
450      * <li>if the given resource is not supervised by this subscriber</li>
451      * <li>if the given resource is a closed project (they are ineligible for
452      * synchronization)</li>
453      * </ul>
454      * <p>
455      * Subclasses may override.
456      * @param traversals the traversals to be refreshed
457      * @param monitor a progress monitor
458      * @throws TeamException if errors occur
459      * @since 3.2
460      */

461     public void refresh(ResourceTraversal[] traversals, IProgressMonitor monitor) throws TeamException {
462         monitor.beginTask(null, 100 * traversals.length);
463         for (int i = 0; i < traversals.length; i++) {
464             ResourceTraversal traversal = traversals[i];
465             refresh(traversal.getResources(), traversal.getDepth(), Policy.subMonitorFor(monitor, 100));
466         }
467         monitor.done();
468     }
469     
470     /**
471      * Return the synchronization state of the given resource mapping.
472      * Only return the portion of the synchronization state that matches
473      * the provided stateMask. The synchronization state flags that are
474      * guaranteed to be interpreted by this method are:
475      * <ul>
476      * <li>The kind flags {@link IDiff#ADD}, {@link IDiff#REMOVE} and {@link IDiff#CHANGE}.
477      * If none of these flags are included then all are assumed.
478      * <li>The direction flags {@link IThreeWayDiff#INCOMING} and {@link IThreeWayDiff#OUTGOING} if the
479      * subscriber is a three-way subscriber. If neither are provided, both are assumed.
480      * </ul>
481      * Other flags can be included and may or may not be interpreted by the subscriber.
482      * <p>
483      * An element will only include {@link IDiff#ADD} in the returned state if all resources covered
484      * by the traversals mappings are added. Similarly, {@link IDiff#REMOVE} will only be included
485      * if all the resources covered by the tarversals are deleted. Otherwise {@link IDiff#CHANGE}
486      * will be returned.
487      *
488      * @param mapping the resource mapping whose synchronization state is to be determined
489      * @param stateMask the mask that identifies the state flags of interested
490      * @param monitor a progress monitor
491      * @return the synchronization state of the given resource mapping
492      * @throws CoreException
493      * @since 3.2
494      * @see IDiff
495      * @see IThreeWayDiff
496      */

497     public int getState(ResourceMapping mapping, int stateMask, IProgressMonitor monitor) throws CoreException {
498         ResourceTraversal[] traversals = mapping.getTraversals(new SubscriberResourceMappingContext(this, true), monitor);
499         final int[] direction = new int[] { 0 };
500         final int[] kind = new int[] { 0 };
501         accept(traversals, new IDiffVisitor() {
502             public boolean visit(IDiff diff) {
503                 if (diff instanceof IThreeWayDiff) {
504                     IThreeWayDiff twd = (IThreeWayDiff) diff;
505                     direction[0] |= twd.getDirection();
506                 }
507                 // If the traversals contain a combination of kinds, return a CHANGE
508
int diffKind = diff.getKind();
509                 if (kind[0] == 0)
510                     kind[0] = diffKind;
511                 if (kind[0] != diffKind) {
512                     kind[0] = IDiff.CHANGE;
513                 }
514                 // Only need to visit the childen of a change
515
return diffKind == IDiff.CHANGE;
516             }
517         });
518         return (direction[0] | kind[0]) & stateMask;
519     }
520 }
521
Popular Tags