KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > core > variants > ThreeWaySynchronizer


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.variants;
12
13 import java.util.*;
14
15 import org.eclipse.core.resources.*;
16 import org.eclipse.core.runtime.*;
17 import org.eclipse.core.runtime.jobs.ILock;
18 import org.eclipse.core.runtime.jobs.ISchedulingRule;
19 import org.eclipse.team.core.TeamException;
20 import org.eclipse.team.internal.core.Policy;
21 import org.eclipse.team.internal.core.subscribers.BatchingLock;
22 import org.eclipse.team.internal.core.subscribers.SyncByteConverter;
23 import org.eclipse.team.internal.core.subscribers.BatchingLock.IFlushOperation;
24 import org.eclipse.team.internal.core.subscribers.BatchingLock.ThreadInfo;
25
26 /**
27  * This class manages the synchronization between local resources and their
28  * corresponding resource variants. It provides the following:
29  * <ul>
30  * <li>Three way synchronization (set base, set remote, ignored)
31  * <li>Resource traversal (members)
32  * <li>Change events and event batching (run)
33  * <li>Thread-safety
34  * </ul>
35  *
36  * @since 3.0
37  */

38 public class ThreeWaySynchronizer implements IFlushOperation {
39
40     private static final byte[] IGNORED_BYTES = "i".getBytes(); //$NON-NLS-1$
41

42     private ILock lock = Platform.getJobManager().newLock();
43     private BatchingLock batchingLock = new BatchingLock();
44     private ResourceVariantByteStore cache;
45     private Set listeners = new HashSet();
46     
47     /**
48      * Create a three-way synchronizer that uses a persistent
49      * byte store with the given qualified name as its unique
50      * identifier.
51      * @param name the unique identifier for the persistent store
52      */

53     public ThreeWaySynchronizer(QualifiedName name) {
54         this(new PersistantResourceVariantByteStore(name));
55     }
56     
57     /**
58      * Create a three-way synchronizer that uses the given byte store
59      * as its underlying byte cache.
60      * @param store the byte store this synchronizer uses to cache its bytes
61      */

62     public ThreeWaySynchronizer(ResourceVariantByteStore store) {
63         cache = store;
64     }
65
66     /**
67      * Adds a listener to this synchronizer. Listeners will be notified
68      * when the synchronization state of a resource changes. Listeners
69      * are not notified when files are modified locally. Clients can
70      * make use of the <code>IResource</code> delta mechanism if they
71      * need to know about local modifications.
72      * Has no effect if an identical listener is already registered.
73      * <p>
74      * Team resource change listeners are informed about state changes
75      * that affect the resources supervised by this subscriber.</p>
76      *
77      * @param listener a synchronizer change listener
78      */

79     public void addListener(ISynchronizerChangeListener listener) {
80         synchronized (listeners) {
81             listeners.add(listener);
82         }
83     }
84
85     /**
86      * Removes a listener previously registered with this synchronizer.
87      * Has no affect if an identical listener is not registered.
88      *
89      * @param listener a synchronizer change listener
90      */

91     public void removeListener(ISynchronizerChangeListener listener) {
92         synchronized (listeners) {
93             listeners.remove(listener);
94         }
95     }
96     
97     /**
98      * Return the base bytes that are cached for the given resource
99      * or <code>null</code> if no base is cached. The returned bytes
100      * should uniquely identify the resource variant that is the base
101      * for the given local resource.
102      *
103      * @param resource the resource
104      * @return the base bytes cached with the resource or <code>null</code>
105      * @throws TeamException
106      */

107     public byte[] getBaseBytes(IResource resource) throws TeamException {
108         try {
109             beginOperation();
110             byte[] syncBytes = internalGetSyncBytes(resource);
111             if (syncBytes == null) return null;
112             byte[] baseBytes = getSlot(syncBytes, 1);
113             if (baseBytes == null || baseBytes.length == 0) return null;
114             return baseBytes;
115         } finally {
116             endOperation();
117         }
118     }
119
120     /**
121      * Set the base bytes for the given resource. The provided bytes
122      * should encode enough information to uniquely identify
123      * (and possibly recreate) the resource variant that is the base
124      * for the given local resource. In essence, setting the base
125      * bytes is equivalent to marking the file as in-sync. As such,
126      * setting the base bytes will also set the remote bytes and mark
127      * the file as clean (i.e. having no outgoing changes).
128      *
129      * @param resource the resource
130      * @param baseBytes the base bytes that identify the base resource variant
131      * @throws TeamException
132      */

133     public void setBaseBytes(IResource resource, byte[] baseBytes) throws TeamException {
134         Assert.isNotNull(baseBytes);
135         ISchedulingRule rule = null;
136         try {
137             rule = beginBatching(resource, null);
138             try {
139                 beginOperation();
140                 String JavaDoc base = new String JavaDoc(baseBytes);
141                 String JavaDoc[] slots = new String JavaDoc[] {
142                         new Long JavaDoc(resource.getModificationStamp()).toString(),
143                         base,
144                         base
145                 };
146                 byte[] syncBytes = toBytes(slots);
147                 internalSetSyncBytes(resource, syncBytes);
148                 batchingLock.resourceChanged(resource);
149             } finally {
150                 endOperation();
151             }
152         } finally {
153             if (rule != null) endBatching(rule, null);
154         }
155     }
156
157     /**
158      * Return whether the local resource has been modified since the last time
159      * the base bytes were set. This method will return <code>false</code>
160      * for ignored resources and <code>true</code> for non-existant resources
161      * that have base bytes cached.
162      * @param resource the resource
163      * @return <code>true</code> if the resource has been modified since the
164      * last time the base bytes were set.
165      * @throws TeamException
166      */

167     public boolean isLocallyModified(IResource resource) throws TeamException {
168         return ((internalGetSyncBytes(resource) == null && ! isIgnored(resource)) ||
169                 (getLocalTimestamp(resource) != resource.getModificationStamp()) ||
170                 (getBaseBytes(resource) != null && !resource.exists()));
171     }
172     
173     /**
174      * Return the remote bytes that are cached for the given resource
175      * or <code>null</code> if no remote is cached. The returned bytes
176      * should uniquely identify the resource variant that is the remote
177      * for the given local resource.
178      *
179      * @param resource the resource
180      * @return the remote bytes cached with the resource or <code>null</code>
181      * @throws TeamException
182      */

183     public byte[] getRemoteBytes(IResource resource) throws TeamException {
184         try {
185             beginOperation();
186             byte[] syncBytes = internalGetSyncBytes(resource);
187             if (syncBytes == null) return null;
188             byte[] remoteBytes = getSlot(syncBytes, 2);
189             if (remoteBytes == null || remoteBytes.length == 0) return null;
190             return remoteBytes;
191         } finally {
192             endOperation();
193         }
194     }
195     
196     /**
197      * Set the remote bytes for the given resource. The provided bytes
198      * should encode enough information to uniquely identify
199      * (and possibly recreate) the resource variant that is the remote
200      * for the given local resource. If the remote for a resource
201      * no longer exists, <code>removeRemoteBytes(IResource)</code>
202      * should be called.
203      *
204      * @param resource the resource
205      * @param remoteBytes the base bytes that identify the remote resource variant
206      * @return <code>true</code> if the remote bytes changed as a result of the set
207      * @throws TeamException
208      */

209     public boolean setRemoteBytes(IResource resource, byte[] remoteBytes) throws TeamException {
210         Assert.isNotNull(remoteBytes);
211         ISchedulingRule rule = null;
212         try {
213             rule = beginBatching(resource, null);
214             try {
215                 beginOperation();
216                 byte[] syncBytes = internalGetSyncBytes(resource);
217                 if (syncBytes == null) {
218                     String JavaDoc[] slots = new String JavaDoc[] {
219                             "", //$NON-NLS-1$
220
"", //$NON-NLS-1$
221
new String JavaDoc(remoteBytes)
222                     };
223                     syncBytes = toBytes(slots);
224                 } else {
225                     byte[] currentRemote = getSlot(syncBytes, 2);
226                     if (equals(remoteBytes, currentRemote)) return false;
227                     syncBytes = setSlot(syncBytes, 2, remoteBytes);
228                 }
229                 internalSetSyncBytes(resource, syncBytes);
230                 batchingLock.resourceChanged(resource);
231                 return true;
232             } finally {
233                 endOperation();
234             }
235         } finally {
236             if (rule != null) endBatching(rule, null);
237         }
238     }
239
240     /**
241      * Remove the remote bytes associated with the resource. This is typically
242      * done when the corresponding remote resource variant no longer exists.
243      * @param resource the resource
244      * @return <code>true</code> if the remote bytes changed as a result of the removal
245      * @throws TeamException
246      */

247     public boolean removeRemoteBytes(IResource resource) throws TeamException {
248         ISchedulingRule rule = null;
249         try {
250             rule = beginBatching(resource, null);
251             try {
252                 beginOperation();
253                 byte[] syncBytes = internalGetSyncBytes(resource);
254                 if (syncBytes != null) {
255                     String JavaDoc currentRemote = new String JavaDoc(getSlot(syncBytes, 2));
256                     if (currentRemote.length() == 0) return false;
257                     syncBytes = setSlot(syncBytes, 2, new byte[0]);
258                     internalSetSyncBytes(resource, syncBytes);
259                     batchingLock.resourceChanged(resource);
260                     return true;
261                 }
262                 return false;
263             } finally {
264                 endOperation();
265             }
266         } finally {
267             if (rule != null) endBatching(rule, null);
268         }
269     }
270     
271     /**
272      * Return whether the given resource has sync bytes in the synchronizer.
273      * @param resource the local resource
274      * @return whether there are sync bytes cached for the local resources.
275      */

276     public boolean hasSyncBytes(IResource resource) throws TeamException {
277         return internalGetSyncBytes(resource) != null;
278     }
279     
280     /**
281      * Returns whether the resource has been marked as ignored
282      * using <code>setIgnored(IResource)</code>.
283      * @param resource the resource
284      * @return <code>true</code> if the resource is ignored.
285      * @throws TeamException
286      */

287     public boolean isIgnored(IResource resource) throws TeamException {
288         byte[] bytes = cache.getBytes(resource);
289         return (bytes != null && equals(bytes, IGNORED_BYTES));
290     }
291     
292     /**
293      * Mark the resource as being ignored. Ignored resources
294      * are not returned by the <code>members</code> method,
295      * are never dirty (see <code>isLocallyModified</code>) and
296      * do not have base or remote bytes cached for them.
297      * @param resource the resource to be ignored
298      * @throws TeamException
299      */

300     public void setIgnored(IResource resource) throws TeamException {
301         internalSetSyncBytes(resource, IGNORED_BYTES);
302     }
303
304     /**
305      * Return the members of the local resource that either have sync bytes
306      * or exist locally and are not ignored.
307      * @param resource the local resource
308      * @return the children of the local resource that have cached sync bytes
309      * or are not ignored
310      * @throws TeamException
311      */

312     public IResource[] members(IResource resource) throws TeamException {
313         if (resource.getType() == IResource.FILE) {
314             return new IResource[0];
315         }
316         try {
317             Set potentialChildren = new HashSet();
318             IContainer container = (IContainer)resource;
319             if (container.exists()) {
320                 potentialChildren.addAll(Arrays.asList(container.members()));
321             }
322             potentialChildren.addAll(Arrays.asList(cache.members(resource)));
323             List result = new ArrayList();
324             for (Iterator iter = potentialChildren.iterator(); iter.hasNext();) {
325                 IResource child = (IResource) iter.next();
326                 if (child.exists() || hasSyncBytes(child)) {
327                     result.add(child);
328                 }
329             }
330             return (IResource[]) result.toArray(new IResource[result.size()]);
331         } catch (CoreException e) {
332             throw TeamException.asTeamException(e);
333         }
334     }
335
336     /**
337      * Flush any cached bytes for the given resource to the depth specified.
338      * @param resource the resource
339      * @param depth the depth of the flush (one of <code>IResource.DEPTH_ZERO</code>,
340      * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
341      * @throws TeamException
342      */

343     public void flush(IResource resource, int depth) throws TeamException {
344         ISchedulingRule rule = null;
345         try {
346             rule = beginBatching(resource, null);
347             try {
348                 beginOperation();
349                 if (cache.flushBytes(resource, depth)) {
350                     batchingLock.resourceChanged(resource);
351                 }
352             } finally {
353                 endOperation();
354             }
355         } finally {
356             if (rule != null) endBatching(rule, null);
357         }
358     }
359
360     /**
361      * Perform multiple sync state modifications and fire only a single change notification
362      * at the end.
363      * @param resourceRule the scheduling rule that encompasses all modifications
364      * @param runnable the runnable that performs the sync state modifications
365      * @param monitor a progress monitor
366      * @throws TeamException
367      */

368     public void run(IResource resourceRule, IWorkspaceRunnable runnable, IProgressMonitor monitor) throws TeamException {
369         monitor = Policy.monitorFor(monitor);
370         monitor.beginTask(null, 100);
371         ISchedulingRule rule = beginBatching(resourceRule, Policy.subMonitorFor(monitor, 10));
372         try {
373             cache.run(resourceRule, runnable, Policy.subMonitorFor(monitor, 80));
374         } catch (CoreException e) {
375             throw TeamException.asTeamException(e);
376         } finally {
377             if (rule != null) endBatching(rule, Policy.subMonitorFor(monitor, 10));
378             monitor.done();
379         }
380     }
381     
382     /* (non-Javadoc)
383      *
384      * Callback which is invoked when the batching resource lock is released
385      * or when a flush is requested (see beginBatching(IResource)).
386      *
387      * @see org.eclipse.team.internal.ftp.deployment.BatchingLock.IFlushOperation#flush(org.eclipse.team.internal.ftp.deployment.BatchingLock.ThreadInfo, org.eclipse.core.runtime.IProgressMonitor)
388      */

389     public void flush(ThreadInfo info, IProgressMonitor monitor) throws TeamException {
390         if (info != null && !info.isEmpty()) {
391             broadcastSyncChanges(info.getChangedResources());
392         }
393     }
394
395     private void broadcastSyncChanges(final IResource[] resources) {
396         ISynchronizerChangeListener[] allListeners;
397         // Copy the listener list so we're not calling client code while synchronized
398
synchronized(listeners) {
399             allListeners = (ISynchronizerChangeListener[]) listeners.toArray(new ISynchronizerChangeListener[listeners.size()]);
400         }
401         // Notify the listeners safely so all will receive notification
402
for (int i = 0; i < allListeners.length; i++) {
403             final ISynchronizerChangeListener listener = allListeners[i];
404             Platform.run(new ISafeRunnable() {
405                 public void handleException(Throwable JavaDoc exception) {
406                     // don't log the exception....it is already being logged in Platform#run
407
}
408                 public void run() throws Exception JavaDoc {
409                     listener.syncStateChanged(resources);
410
411                 }
412             });
413         }
414     }
415     
416     /*
417      * Return the cached sync bytes for the given resource.
418      * The value <code>null</code> is returned if there is no
419      * cached bytes or if the resource is ignored.
420      */

421     private byte[] internalGetSyncBytes(IResource resource) throws TeamException {
422         byte[] bytes = cache.getBytes(resource);
423         if (bytes != null && equals(bytes, IGNORED_BYTES)) return null;
424         return bytes;
425     }
426     
427     /*
428      * Set the cached sync bytes
429      */

430     private boolean internalSetSyncBytes(IResource resource, byte[] syncBytes) throws TeamException {
431         return cache.setBytes(resource, syncBytes);
432     }
433     
434     private byte[] getSlot(byte[] syncBytes, int i) {
435         return SyncByteConverter.getSlot(syncBytes, i, false);
436     }
437     
438     private byte[] setSlot(byte[] syncBytes, int i, byte[] insertBytes) throws TeamException {
439         return SyncByteConverter.setSlot(syncBytes, i, insertBytes);
440     }
441     
442     private byte[] toBytes(String JavaDoc[] slots) {
443         return SyncByteConverter.toBytes(slots);
444     }
445     
446     private long getLocalTimestamp(IResource resource) throws TeamException {
447         try {
448             beginOperation();
449             byte[] syncBytes = internalGetSyncBytes(resource);
450             if (syncBytes == null) return -1;
451             byte[] bytes = getSlot(syncBytes, 0);
452             if (bytes == null || bytes.length == 0) return -1;
453             return Long.parseLong(new String JavaDoc(bytes));
454         } finally {
455             endOperation();
456         }
457     }
458     
459     private boolean equals(byte[] syncBytes, byte[] oldBytes) {
460         if (syncBytes.length != oldBytes.length) return false;
461         for (int i = 0; i < oldBytes.length; i++) {
462             if (oldBytes[i] != syncBytes[i]) return false;
463         }
464         return true;
465     }
466     
467     /*
468      * Begin an access to the internal data structures of the synchronizer
469      */

470     private void beginOperation() {
471         // Do not try to acquire the lock if the resources tree is locked
472
// The reason for this is that during the resource delta phase (i.e. when the tree is locked)
473
// the workspace lock is held. If we obtain our lock, there is
474
// a chance of deadlock. It is OK if we don't as we are still protected
475
// by scheduling rules and the workspace lock.
476
if (ResourcesPlugin.getWorkspace().isTreeLocked()) return;
477         lock.acquire();
478     }
479     
480     /*
481      * End an access to the internal data structures of the synchronizer
482      */

483     private void endOperation() {
484         // See beginOperation() for a description of why the lock is not obtained when the tree is locked
485
if (ResourcesPlugin.getWorkspace().isTreeLocked()) return;
486         lock.release();
487     }
488     
489     /*
490      * Begins a batch of operations in order to batch event changes.
491      * The provided scheduling rule indicates the resources
492      * that the resources affected by the operation while the returned scheduling rule
493      * is the rule obtained by the lock. It may differ from the provided rule.
494      */

495     private ISchedulingRule beginBatching(ISchedulingRule resourceRule, IProgressMonitor monitor) {
496         return batchingLock.acquire(resourceRule, this /* IFlushOperation */, monitor);
497     }
498     
499     /*
500      * Ends a batch of operations. The provided rule must be the one that was returned
501      * by the corresponding call to beginBatching.
502      */

503     private void endBatching(ISchedulingRule rule, IProgressMonitor monitor) throws TeamException {
504         batchingLock.release(rule, monitor);
505     }
506 }
507
Popular Tags