KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > core > synchronize > SyncInfoSet


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 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.synchronize;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Collections JavaDoc;
15 import java.util.HashMap JavaDoc;
16 import java.util.HashSet JavaDoc;
17 import java.util.Iterator JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.Map JavaDoc;
20 import java.util.Set JavaDoc;
21
22 import org.eclipse.core.resources.IResource;
23 import org.eclipse.core.resources.IWorkspaceRunnable;
24 import org.eclipse.core.runtime.*;
25 import org.eclipse.core.runtime.jobs.ILock;
26 import org.eclipse.core.runtime.jobs.Job;
27 import org.eclipse.team.core.ITeamStatus;
28 import org.eclipse.team.core.TeamStatus;
29 import org.eclipse.team.core.synchronize.FastSyncInfoFilter.SyncInfoDirectionFilter;
30 import org.eclipse.team.internal.core.Policy;
31 import org.eclipse.team.internal.core.TeamPlugin;
32 import org.eclipse.team.internal.core.subscribers.SyncInfoStatistics;
33 import org.eclipse.team.internal.core.subscribers.SyncSetChangedEvent;
34
35 /**
36  * A dynamic collection of {@link SyncInfo} objects that provides
37  * change notification to registered listeners. Batching of change notifications
38  * can be accomplished using the <code>beginInput/endInput</code> methods.
39  *
40  * @see SyncInfoTree
41  * @see SyncInfo
42  * @see ISyncInfoSetChangeListener
43  * @since 3.0
44  */

45 public class SyncInfoSet {
46     // fields used to hold resources of interest
47
// {IPath -> SyncInfo}
48
private Map JavaDoc resources = Collections.synchronizedMap(new HashMap JavaDoc());
49
50     // keep track of number of sync kinds in the set
51
private SyncInfoStatistics statistics = new SyncInfoStatistics();
52     
53     // keep track of errors that occurred while trying to populate the set
54
private Map JavaDoc errors = new HashMap JavaDoc();
55     
56     private boolean lockedForModification;
57     
58     /**
59      * Create an empty set.
60      */

61     public SyncInfoSet() {
62     }
63
64     /**
65      * Create a <code>SyncInfoSet</code> containing the given <code>SyncInfo</code>
66      * instances.
67      *
68      * @param infos the <code>SyncInfo</code> instances to be contained by this set
69      */

70     public SyncInfoSet(SyncInfo[] infos) {
71         this();
72         // use the internal add since we can't have listeners at this point anyway
73
for (int i = 0; i < infos.length; i++) {
74             internalAdd(infos[i]);
75         }
76     }
77     
78     /**
79      * Return an array of <code>SyncInfo</code> for all out-of-sync resources that are contained by the set.
80      *
81      * @return an array of <code>SyncInfo</code>
82      */

83     public synchronized SyncInfo[] getSyncInfos() {
84         return (SyncInfo[]) resources.values().toArray(new SyncInfo[resources.size()]);
85     }
86     
87     /**
88      * Return all out-of-sync resources contained in this set. The default implementation
89      * uses <code>getSyncInfos()</code> to determine the resources contained in the set.
90      * Subclasses may override to optimize.
91      *
92      * @return all out-of-sync resources contained in the set
93      */

94     public IResource[] getResources() {
95         SyncInfo[] infos = getSyncInfos();
96         List JavaDoc resources = new ArrayList JavaDoc();
97         for (int i = 0; i < infos.length; i++) {
98             SyncInfo info = infos[i];
99             resources.add(info.getLocal());
100         }
101         return (IResource[]) resources.toArray(new IResource[resources.size()]);
102     }
103     
104     /**
105      * Return the <code>SyncInfo</code> for the given resource or <code>null</code>
106      * if the resource is not contained in the set.
107      *
108      * @param resource the resource
109      * @return the <code>SyncInfo</code> for the resource or <code>null</code> if
110      * the resource is in-sync or doesn't have synchronization information in this set.
111      */

112     public synchronized SyncInfo getSyncInfo(IResource resource) {
113         return (SyncInfo)resources.get(resource.getFullPath());
114     }
115
116     /**
117      * Return the number of out-of-sync resources contained in this set.
118      *
119      * @return the size of the set.
120      * @see #countFor(int, int)
121      */

122     public synchronized int size() {
123         return resources.size();
124     }
125
126     /**
127      * Return the number of out-of-sync resources in the given set whose sync kind
128      * matches the given kind and mask (e.g. <code>(SyncInfo#getKind() & mask) == kind</code>).
129      * <p>
130      * For example, this will return the number of outgoing changes in the set:
131      * <pre>
132      * long outgoing = countFor(SyncInfo.OUTGOING, SyncInfo.DIRECTION_MASK);
133      * </pre>
134      * </p>
135      * @param kind the sync kind
136      * @param mask the sync kind mask
137      * @return the number of matching resources in the set.
138      */

139     public long countFor(int kind, int mask) {
140         return statistics.countFor(kind, mask);
141     }
142     
143     /**
144      * Returns <code>true</code> if there are any conflicting nodes in the set, and
145      * <code>false</code> otherwise.
146      *
147      * @return <code>true</code> if there are any conflicting nodes in the set, and
148      * <code>false</code> otherwise.
149      */

150     public boolean hasConflicts() {
151         return countFor(SyncInfo.CONFLICTING, SyncInfo.DIRECTION_MASK) > 0;
152     }
153     
154     /**
155      * Return whether the set is empty.
156      *
157      * @return <code>true</code> if the set is empty
158      */

159     public synchronized boolean isEmpty() {
160         return resources.isEmpty();
161     }
162     
163     /**
164      * Add the <code>SyncInfo</code> to the set, replacing any previously existing one.
165      *
166      * @param info the new <code>SyncInfo</code>
167      */

168     protected synchronized void internalAdd(SyncInfo info) {
169         Assert.isTrue(!lockedForModification);
170         IResource local = info.getLocal();
171         IPath path = local.getFullPath();
172         SyncInfo oldSyncInfo = (SyncInfo)resources.put(path, info);
173         if(oldSyncInfo == null) {
174             statistics.add(info);
175         } else {
176             statistics.remove(oldSyncInfo);
177             statistics.add(info);
178         }
179     }
180     
181     /**
182      * Remove the resource from the set, updating all internal data structures.
183      *
184      * @param resource the resource to be removed
185      * @return the <code>SyncInfo</code> that was just removed
186      */

187     protected synchronized SyncInfo internalRemove(IResource resource) {
188         Assert.isTrue(!lockedForModification);
189         IPath path = resource.getFullPath();
190         SyncInfo info = (SyncInfo)resources.remove(path);
191         if (info != null) {
192             statistics.remove(info);
193         }
194         return info;
195     }
196     
197     /**
198      * Registers the given listener for sync info set notifications. Has
199      * no effect if an identical listener is already registered.
200      *
201      * @param listener listener to register
202      */

203     public void addSyncSetChangedListener(ISyncInfoSetChangeListener listener) {
204         synchronized(listeners) {
205             listeners.add(listener);
206         }
207     }
208
209     /**
210      * Removes the given listener from participant notifications. Has
211      * no effect if listener is not already registered.
212      *
213      * @param listener listener to remove
214      */

215     public void removeSyncSetChangedListener(ISyncInfoSetChangeListener listener) {
216         synchronized(listeners) {
217             listeners.remove(listener);
218         }
219     }
220     
221     /**
222      * Reset the sync set so it is empty. Listeners are notified of the change.
223      */

224     public void clear() {
225         try {
226             beginInput();
227             errors.clear();
228             resources.clear();
229             statistics.clear();
230             getChangeEvent().reset();
231         } finally {
232             endInput(null);
233         }
234     }
235
236     /*
237      * Run the given runnable. This operation
238      * will block other threads from modifying the
239      * set and postpone any change notifications until after the runnable
240      * has been executed. Mutable subclasses must override.
241      * <p>
242      * The given runnable may be run in the same thread as the caller or
243      * more be run asynchronously in another thread at the discretion of the
244      * subclass implementation. However, it is guaranteed that two invocations
245      * of <code>run</code> performed in the same thread will be executed in the
246      * same order even if run in different threads.
247      * </p>
248      * @param runnable a runnable
249      * @param progress a progress monitor or <code>null</code>
250      */

251     private void run(IWorkspaceRunnable runnable, IProgressMonitor monitor) {
252         monitor = Policy.monitorFor(monitor);
253         monitor.beginTask(null, 100);
254         try {
255             beginInput();
256             runnable.run(Policy.subMonitorFor(monitor, 80));
257         } catch (CoreException e) {
258             addError(new TeamStatus(IStatus.ERROR, TeamPlugin.ID, ITeamStatus.SYNC_INFO_SET_ERROR, e.getMessage(), e, null));
259         } finally {
260             endInput(Policy.subMonitorFor(monitor, 20));
261         }
262     }
263     
264     /**
265      * Connect the listener to the sync set in such a fashion that the listener will
266      * be connected the the sync set using <code>addChangeListener</code>
267      * and issued a reset event. This is done to provide a means of connecting to the
268      * sync set and initializing a model based on the sync set without worrying about
269      * missing events.
270      * <p>
271      * The reset event may be done in the context of this method invocation or may be
272      * done in another thread at the discretion of the <code>SyncInfoSet</code>
273      * implementation.
274      * </p><p>
275      * Disconnecting is done by calling <code>removeChangeListener</code>. Once disconnected,
276      * a listener can reconnect to be re-initialized.
277      * </p>
278      * @param listener the listener that should be connected to this set
279      * @param monitor a progress monitor
280      */

281     public void connect(final ISyncInfoSetChangeListener listener, IProgressMonitor monitor) {
282         run(new IWorkspaceRunnable() {
283             public void run(IProgressMonitor monitor) {
284                 try {
285                     monitor.beginTask(null, 100);
286                     addSyncSetChangedListener(listener);
287                     listener.syncInfoSetReset(SyncInfoSet.this, Policy.subMonitorFor(monitor, 95));
288                 } finally {
289                     monitor.done();
290                 }
291             }
292         }, monitor);
293     }
294
295     private ILock lock = Job.getJobManager().newLock();
296
297     private Set JavaDoc listeners = Collections.synchronizedSet(new HashSet JavaDoc());
298
299     private SyncSetChangedEvent changes = createEmptyChangeEvent();
300
301     /**
302      * Add the given <code>SyncInfo</code> to the set. A change event will
303      * be generated unless the call to this method is nested in between calls
304      * to <code>beginInput()</code> and <code>endInput(IProgressMonitor)</code>
305      * in which case the event for this addition and any other sync set
306      * change will be fired in a batched event when <code>endInput</code>
307      * is invoked.
308      * <p>
309      * Invoking this method outside of the above mentioned block will result
310      * in the <code>endInput(IProgressMonitor)</code> being invoked with a null
311      * progress monitor. If responsiveness is required, the client should always
312      * nest sync set modifications within <code>beginInput/endInput</code>.
313      * </p>
314      * @param info the sync info to be added to this set.
315      */

316     public void add(SyncInfo info) {
317         try {
318             beginInput();
319             boolean alreadyExists = getSyncInfo(info.getLocal()) != null;
320             internalAdd(info);
321             if (alreadyExists) {
322                 getChangeEvent().changed(info);
323             } else {
324                 getChangeEvent().added(info);
325             }
326         } finally {
327             endInput(null);
328         }
329     }
330
331     /**
332      * Add all the sync info from the given set to this set.
333      *
334      * @param set the set whose sync info should be added to this set
335      */

336     public void addAll(SyncInfoSet set) {
337         try {
338             beginInput();
339             SyncInfo[] infos = set.getSyncInfos();
340             for (int i = 0; i < infos.length; i++) {
341                 add(infos[i]);
342             }
343         } finally {
344             endInput(null);
345         }
346     }
347
348     /**
349      * Remove the given local resource from the set.
350      *
351      * @param resource the local resource to remove
352      */

353     public synchronized void remove(IResource resource) {
354         try {
355             beginInput();
356             SyncInfo info = internalRemove(resource);
357             getChangeEvent().removed(resource, info);
358         } finally {
359             endInput(null);
360         }
361     }
362
363     /**
364      * Remove all the given resources from the set.
365      *
366      * @param resources the resources to be removed
367      */

368     public void removeAll(IResource[] resources) {
369         try {
370             beginInput();
371             for (int i = 0; i < resources.length; i++) {
372                 remove(resources[i]);
373             }
374         } finally {
375             endInput(null);
376         }
377     }
378
379     /**
380      * Removes all conflicting nodes from this set.
381      */

382     public void removeConflictingNodes() {
383         rejectNodes(new SyncInfoDirectionFilter(SyncInfo.CONFLICTING));
384     }
385
386     /**
387      * Removes all outgoing nodes from this set.
388      */

389     public void removeOutgoingNodes() {
390         rejectNodes(new SyncInfoDirectionFilter(SyncInfo.OUTGOING));
391     }
392
393     /**
394      * Removes all incoming nodes from this set.
395      */

396     public void removeIncomingNodes() {
397         rejectNodes(new SyncInfoDirectionFilter(SyncInfo.INCOMING));
398     }
399
400     /**
401      * Indicate whether the set has nodes matching the given filter.
402      *
403      * @param filter a sync info filter
404      * @return whether the set has nodes that match the filter
405      */

406     public boolean hasNodes(FastSyncInfoFilter filter) {
407         SyncInfo[] infos = getSyncInfos();
408         for (int i = 0; i < infos.length; i++) {
409             SyncInfo info = infos[i];
410             if (info != null && filter.select(info)) {
411                 return true;
412             }
413         }
414         return false;
415     }
416
417     /**
418      * Removes all nodes from this set that do not match the given filter
419      * leaving only those that do match the filter.
420      *
421      * @param filter a sync info filter
422      */

423     public void selectNodes(FastSyncInfoFilter filter) {
424         try {
425             beginInput();
426             SyncInfo[] infos = getSyncInfos();
427             for (int i = 0; i < infos.length; i++) {
428                 SyncInfo info = infos[i];
429                 if (info == null || !filter.select(info)) {
430                     remove(info.getLocal());
431                 }
432             }
433         } finally {
434             endInput(null);
435         }
436     }
437
438     /**
439      * Removes all nodes from this set that match the given filter
440      * leaving those that do not match the filter.
441      *
442      * @param filter a sync info filter
443      */

444     public void rejectNodes(FastSyncInfoFilter filter) {
445         try {
446             beginInput();
447             SyncInfo[] infos = getSyncInfos();
448             for (int i = 0; i < infos.length; i++) {
449                 SyncInfo info = infos[i];
450                 if (info != null && filter.select(info)) {
451                     remove(info.getLocal());
452                 }
453             }
454         } finally {
455             endInput(null);
456         }
457     }
458
459     /**
460      * Return all nodes in this set that match the given filter.
461      *
462      * @param filter a sync info filter
463      * @return the nodes that match the filter
464      */

465     public SyncInfo[] getNodes(FastSyncInfoFilter filter) {
466         List JavaDoc result = new ArrayList JavaDoc();
467         SyncInfo[] infos = getSyncInfos();
468         for (int i = 0; i < infos.length; i++) {
469             SyncInfo info = infos[i];
470             if (info != null && filter.select(info)) {
471                 result.add(info);
472             }
473         }
474         return (SyncInfo[]) result.toArray(new SyncInfo[result.size()]);
475     }
476
477     /**
478      * Returns <code>true</code> if this sync set has incoming changes.
479      * Note that conflicts are not considered to be incoming changes.
480      *
481      * @return <code>true</code> if this sync set has incoming changes.
482      */

483     public boolean hasIncomingChanges() {
484         return countFor(SyncInfo.INCOMING, SyncInfo.DIRECTION_MASK) > 0;
485     }
486
487     /**
488      * Returns <code>true</code> if this sync set has outgoing changes.
489      * Note that conflicts are not considered to be outgoing changes.
490      *
491      * @return <code>true</code> if this sync set has outgoing changes.
492      */

493     public boolean hasOutgoingChanges() {
494         return countFor(SyncInfo.OUTGOING, SyncInfo.DIRECTION_MASK) > 0;
495     }
496
497     /**
498      * This method is used to obtain a lock on the set which ensures thread safety
499      * and batches change notification. If the set is locked by another thread,
500      * the calling thread will block until the lock
501      * becomes available. This method uses an <code>org.eclipse.core.runtime.jobs.ILock</code>.
502      * <p>
503      * It is important that the lock is released after it is obtained. Calls to <code>endInput</code>
504      * should be done in a finally block as illustrated in the following code snippet.
505      * <pre>
506      * try {
507      * set.beginInput();
508      * // do stuff
509      * } finally {
510      * set.endInput(progress);
511      * }
512      * </pre>
513      * </p><p>
514      * Calls to <code>beginInput</code> and <code>endInput</code> can be nested and must be matched.
515      * </p>
516      */

517     public void beginInput() {
518         lock.acquire();
519     }
520
521     /**
522      * This method is used to release the lock on this set. The progress monitor is needed to allow
523      * listeners to perform long-running operations is response to the set change. The lock is held
524      * while the listeners are notified so listeners must be cautious in order to avoid deadlock.
525      * @param monitor a progress monitor
526      */

527     public void endInput(IProgressMonitor monitor) {
528         try {
529             if (lock.getDepth() == 1) {
530                 // Remain locked while firing the events so the handlers
531
// can expect the set to remain constant while they process the events
532
fireChanges(Policy.monitorFor(monitor));
533             }
534         } finally {
535             lock.release();
536         }
537     }
538
539     /**
540      * Reset the changes accumulated so far by this set. This method is not
541      * intended to be invoked or implemented by clients.
542      */

543     protected void resetChanges() {
544         changes = createEmptyChangeEvent();
545     }
546
547     /**
548      * Create an empty change event. Subclass may override to provided specialized event types
549      *
550      * @return an empty change event
551      */

552     protected SyncSetChangedEvent createEmptyChangeEvent() {
553         return new SyncSetChangedEvent(this);
554     }
555
556     private void fireChanges(final IProgressMonitor monitor) {
557         // Use a synchronized block to ensure that the event we send is static
558
final SyncSetChangedEvent event;
559         synchronized(this) {
560             event = getChangeEvent();
561             resetChanges();
562         }
563         // Ensure that the list of listeners is not changed while events are fired.
564
// Copy the listeners so that addition/removal is not blocked by event listeners
565
if(event.isEmpty() && ! event.isReset()) return;
566         ISyncInfoSetChangeListener[] allListeners = getListeners();
567         // Fire the events using an ISafeRunnable
568
final ITeamStatus[] newErrors = event.getErrors();
569         monitor.beginTask(null, 100 + (newErrors.length > 0 ? 50 : 0) * allListeners.length);
570         for (int i = 0; i < allListeners.length; i++) {
571             final ISyncInfoSetChangeListener listener = allListeners[i];
572             SafeRunner.run(new ISafeRunnable() {
573                 public void handleException(Throwable JavaDoc exception) {
574                     // don't log the exception....it is already being logged in Platform#run
575
}
576                 public void run() throws Exception JavaDoc {
577                     try {
578                         lockedForModification = true;
579                         if (event.isReset()) {
580                             listener.syncInfoSetReset(SyncInfoSet.this, Policy.subMonitorFor(monitor, 100));
581                         } else {
582                             listener.syncInfoChanged(event, Policy.subMonitorFor(monitor, 100));
583                         }
584                         if (newErrors.length > 0) {
585                             listener.syncInfoSetErrors(SyncInfoSet.this, newErrors, Policy.subMonitorFor(monitor, 50));
586                         }
587                     } finally {
588                         lockedForModification = false;
589                     }
590                 }
591             });
592         }
593         monitor.done();
594     }
595
596     /**
597      * Return a copy of all the listeners registered with this set
598      * @return the listeners
599      */

600     protected ISyncInfoSetChangeListener[] getListeners() {
601         ISyncInfoSetChangeListener[] allListeners;
602         synchronized(listeners) {
603             allListeners = (ISyncInfoSetChangeListener[]) listeners.toArray(new ISyncInfoSetChangeListener[listeners.size()]);
604         }
605         return allListeners;
606     }
607
608     /**
609      * Return the change event that is accumulating the changes to the set.
610      * This can be called by subclasses to access the event.
611      * @return Returns the changes.
612      */

613     protected SyncSetChangedEvent getChangeEvent() {
614         return changes;
615     }
616     
617     /**
618      * Add the error to the set. Errors should be added to the set when the client
619      * populating the set cannot determine the <code>SyncInfo</code> for one
620      * or more resources due to an exception or some other problem. Listeners
621      * will be notified that an error occurred and can react accordingly.
622      * <p>
623      * Only one error can be associated with a resource (which is obtained from
624      * the <code>ITeamStatus</code>). It is up to the
625      * client populating the set to ensure that the error associated with a
626      * resource contains all relevant information.
627      * The error will remain in the set until the set is reset.
628      * </p>
629      * @param status the status that describes the error that occurred.
630      */

631     public void addError(ITeamStatus status) {
632         try {
633             beginInput();
634             errors.put(status.getResource(), status);
635             getChangeEvent().errorOccurred(status);
636         } finally {
637             endInput(null);
638         }
639     }
640     
641     /**
642      * Return an array of the errors the occurred while populating this set.
643      * The errors will remain with the set until it is reset.
644      *
645      * @return the errors
646      */

647     public ITeamStatus[] getErrors() {
648         return (ITeamStatus[]) errors.values().toArray(new ITeamStatus[errors.size()]);
649     }
650
651     /**
652      * Return an iterator over all <code>SyncInfo</code>
653      * contained in this set.
654      * @return an iterator over all <code>SyncInfo</code>
655      * contained in this set.
656      * @since 3.1
657      */

658     public Iterator JavaDoc iterator() {
659         return resources.values().iterator();
660     }
661 }
662
Popular Tags