KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > internal > core > subscribers > BatchingLock


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.internal.core.subscribers;
12
13 import java.util.*;
14
15 import org.eclipse.core.resources.IResource;
16 import org.eclipse.core.runtime.*;
17 import org.eclipse.core.runtime.jobs.*;
18 import org.eclipse.team.core.TeamException;
19 import org.eclipse.team.internal.core.*;
20
21 /**
22  * Provides a per-thread nested locking mechanism. A thread can acquire a
23  * lock on a specific resource by calling acquire(). Subsequently, acquire() can be called
24  * multiple times on the resource or any of its children from within the same thread
25  * without blocking. Other threads that try
26  * and acquire the lock on those same resources will be blocked until the first
27  * thread releases all it's nested locks.
28  * <p>
29  * The locking is managed by the platform via scheduling rules. This class simply
30  * provides the nesting mechanism in order to allow the client to determine when
31  * the lock for the thread has been released. Therefore, this lock will block if
32  * another thread already locks the same resource.</p>
33  */

34 public class BatchingLock {
35
36     private final static boolean DEBUG = Policy.DEBUG_THREADING;
37     
38     // This is a placeholder rule used to indicate that no scheduling rule is needed
39
/* internal use only */ static final ISchedulingRule NULL_SCHEDULING_RULE= new ISchedulingRule() {
40         public boolean contains(ISchedulingRule rule) {
41             return false;
42         }
43         public boolean isConflicting(ISchedulingRule rule) {
44             return false;
45         }
46     };
47     
48     public class ThreadInfo {
49         private Set changedResources = new HashSet();
50         private IFlushOperation operation;
51         private List rules = new ArrayList();
52         public ThreadInfo(IFlushOperation operation) {
53             this.operation = operation;
54         }
55         /**
56          * Push a scheduling rule onto the stack for this thread and
57          * acquire the rule if it is not the workspace root.
58          * @param resource
59          * @param monitor
60          * @return the scheduling rule that was obtained
61          */

62         public ISchedulingRule pushRule(ISchedulingRule resource, IProgressMonitor monitor) {
63             // The scheduling rule is either the project or the resource's parent
64
final ISchedulingRule rule = getRuleForResoure(resource);
65             if (rule != NULL_SCHEDULING_RULE) {
66                 boolean success = false;
67                 try {
68                     Job.getJobManager().beginRule(rule, monitor);
69                     addRule(rule);
70                     success = true;
71                 } finally {
72                     if (!success) {
73                         try {
74                             // The begin was canceled (or some other problem occurred).
75
// Free the scheduling rule
76
// so the clients of ReentrantLock don't need to
77
// do an endRule when the operation is canceled.
78
Job.getJobManager().endRule(rule);
79                         } catch (RuntimeException JavaDoc e) {
80                             // Log and ignore so the original exception is not lost
81
TeamPlugin.log(IStatus.ERROR, "Failed to end scheduling rule", e); //$NON-NLS-1$
82
}
83                     }
84                 }
85             } else {
86                 // Record the fact that we didn't push a rule so we
87
// can match it when we pop
88
addRule(rule);
89             }
90             return rule;
91         }
92         /**
93          * Pop the scheduling rule from the stack and release it if it
94          * is not the workspace root. Flush any changed sync info to
95          * disk if necessary. A flush is necessary if the stack is empty
96          * or if the top-most non-null scheduling rule was popped as a result
97          * of this operation.
98          * @param rule
99          * @param monitor
100          * @throws TeamException
101          */

102         public void popRule(ISchedulingRule rule, IProgressMonitor monitor) throws TeamException {
103             try {
104                 if (isFlushRequired()) {
105                     flush(monitor);
106                 }
107             } finally {
108                 ISchedulingRule stackedRule = removeRule();
109                 if (rule == null) {
110                     rule = NULL_SCHEDULING_RULE;
111                 }
112                 Assert.isTrue(stackedRule.equals(rule), "end for resource '" + rule + "' does not match stacked rule '" + stackedRule + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
113
if (rule != NULL_SCHEDULING_RULE) {
114                     Job.getJobManager().endRule(rule);
115                 }
116             }
117         }
118         private ISchedulingRule getRuleForResoure(ISchedulingRule resourceRule) {
119             ISchedulingRule rule;
120             if (resourceRule instanceof IResource) {
121                 IResource resource = (IResource)resourceRule;
122                 if (resource.getType() == IResource.ROOT) {
123                     // Never lock the whole workspace
124
rule = NULL_SCHEDULING_RULE;
125                 } else if (resource.getType() == IResource.PROJECT) {
126                     rule = resource;
127                 } else {
128                     rule = resource.getParent();
129                 }
130             } else if (resourceRule instanceof MultiRule) {
131                 // Create a MultiRule for all projects from the given rule
132
ISchedulingRule[] rules = ((MultiRule)resourceRule).getChildren();
133                 Set projects = new HashSet();
134                 for (int i = 0; i < rules.length; i++) {
135                     ISchedulingRule childRule = rules[i];
136                     if (childRule instanceof IResource) {
137                         projects.add(((IResource)childRule).getProject());
138                     }
139                 }
140                 if (projects.isEmpty()) {
141                     rule = NULL_SCHEDULING_RULE;
142                 } else if (projects.size() == 1) {
143                     rule = (ISchedulingRule)projects.iterator().next();
144                 } else {
145                     rule = new MultiRule((ISchedulingRule[]) projects.toArray(new ISchedulingRule[projects.size()]));
146                 }
147             } else {
148                 // Rule is not associated with resources so ignore it
149
rule = NULL_SCHEDULING_RULE;
150             }
151             return rule;
152         }
153         /**
154          * Return <code>true</code> if we are still nested in
155          * an acquire for this thread.
156          *
157          * @return whether there are still rules on the stack
158          */

159         public boolean isNested() {
160             return !rules.isEmpty();
161         }
162         public void addChangedResource(IResource resource) {
163             changedResources.add(resource);
164         }
165         public boolean isEmpty() {
166             return changedResources.isEmpty();
167         }
168         public IResource[] getChangedResources() {
169             return (IResource[]) changedResources.toArray(new IResource[changedResources.size()]);
170         }
171         public void flush(IProgressMonitor monitor) throws TeamException {
172             try {
173                 operation.flush(this, monitor);
174             } catch (OutOfMemoryError JavaDoc e) {
175                 throw e;
176             } catch (Error JavaDoc e) {
177                 handleAbortedFlush(e);
178                 throw e;
179             } catch (RuntimeException JavaDoc e) {
180                 handleAbortedFlush(e);
181                 throw e;
182             } finally {
183                 // We have to clear the resources no matter what since the next attempt
184
// to flush may not have an appropriate scheduling rule
185
changedResources.clear();
186             }
187         }
188         private boolean isFlushRequired() {
189             return rules.size() == 1 || remainingRulesAreNull();
190         }
191         /*
192          * Return true if all but the last rule in the stack is null
193          */

194         private boolean remainingRulesAreNull() {
195             for (int i = 0; i < rules.size() - 1; i++) {
196                 ISchedulingRule rule = (ISchedulingRule) rules.get(i);
197                 if (rule != NULL_SCHEDULING_RULE) {
198                     return false;
199                 }
200             }
201             return true;
202         }
203         private void handleAbortedFlush(Throwable JavaDoc t) {
204             TeamPlugin.log(IStatus.ERROR, Messages.BatchingLock_11, t);
205         }
206         private void addRule(ISchedulingRule rule) {
207             rules.add(rule);
208         }
209         private ISchedulingRule removeRule() {
210             return (ISchedulingRule)rules.remove(rules.size() - 1);
211         }
212         public boolean ruleContains(IResource resource) {
213             for (Iterator iter = rules.iterator(); iter.hasNext();) {
214                 ISchedulingRule rule = (ISchedulingRule) iter.next();
215                 if (rule != NULL_SCHEDULING_RULE && rule.contains(resource)) {
216                     return true;
217                 }
218             }
219             return false;
220         }
221     }
222     
223     public interface IFlushOperation {
224         public void flush(ThreadInfo info, IProgressMonitor monitor) throws TeamException;
225     }
226     
227     private Map infos = new HashMap();
228     
229     /**
230      * Return the thread info for the current thread
231      * @return the thread info for the current thread
232      */

233     protected ThreadInfo getThreadInfo() {
234         Thread JavaDoc thisThread = Thread.currentThread();
235         synchronized (infos) {
236             ThreadInfo info = (ThreadInfo)infos.get(thisThread);
237             return info;
238         }
239     }
240     
241     private ThreadInfo getThreadInfo(IResource resource) {
242         synchronized (infos) {
243             for (Iterator iter = infos.values().iterator(); iter.hasNext();) {
244                 ThreadInfo info = (ThreadInfo) iter.next();
245                 if (info.ruleContains(resource)) {
246                     return info;
247                 }
248             }
249             return null;
250         }
251     }
252     
253     public ISchedulingRule acquire(ISchedulingRule resourceRule, IFlushOperation operation, IProgressMonitor monitor) {
254         ThreadInfo info = getThreadInfo();
255         boolean added = false;
256         synchronized (infos) {
257             if (info == null) {
258                 info = createThreadInfo(operation);
259                 Thread JavaDoc thisThread = Thread.currentThread();
260                 infos.put(thisThread, info);
261                 added = true;
262                 if(DEBUG) System.out.println("[" + thisThread.getName() + "] acquired batching lock on " + resourceRule); //$NON-NLS-1$ //$NON-NLS-2$
263
}
264         }
265         try {
266             return info.pushRule(resourceRule, monitor);
267         } catch (OperationCanceledException e) {
268             // The operation was canceled.
269
// If this is the outermost acquire then remove the info that was just added
270
if (added) {
271                 synchronized (infos) {
272                     infos.remove(Thread.currentThread());
273                 }
274             }
275             throw e;
276         }
277     }
278     
279     /**
280      * Create the ThreadInfo instance used to cache the lock state for the
281      * current thread. Subclass can override to provide a subclass of
282      * ThreadInfo.
283      * @param operation the flush operation
284      * @return a ThreadInfo instance
285      */

286     protected ThreadInfo createThreadInfo(IFlushOperation operation) {
287         return new ThreadInfo(operation);
288     }
289
290     /**
291      * Release the lock held on any resources by this thread. The provided rule must
292      * be identical to the rule returned by the corresponding acquire(). If the rule
293      * for the release is non-null and all remaining rules held by the lock are null,
294      * the the flush operation provided in the acquire method will be executed.
295      * @param rule the scheduling rule
296      * @param monitor a progress monitor
297      * @throws TeamException
298      */

299     public void release(ISchedulingRule rule, IProgressMonitor monitor) throws TeamException {
300         ThreadInfo info = getThreadInfo();
301         Assert.isNotNull(info, "Unmatched acquire/release."); //$NON-NLS-1$
302
Assert.isTrue(info.isNested(), "Unmatched acquire/release."); //$NON-NLS-1$
303
info.popRule(rule, monitor);
304         synchronized (infos) {
305             if (!info.isNested()) {
306                 Thread JavaDoc thisThread = Thread.currentThread();
307                 if(DEBUG) System.out.println("[" + thisThread.getName() + "] released batching lock"); //$NON-NLS-1$ //$NON-NLS-2$
308
infos.remove(thisThread);
309             }
310         }
311     }
312
313     public void resourceChanged(IResource resource) {
314         ThreadInfo info = getThreadInfo();
315         Assert.isNotNull(info, "Folder changed outside of resource lock"); //$NON-NLS-1$
316
info.addChangedResource(resource);
317     }
318
319     /**
320      * Flush any changes accumulated by the lock so far.
321      * @param monitor a progress monitor
322      * @throws TeamException
323      */

324     public void flush(IProgressMonitor monitor) throws TeamException {
325         ThreadInfo info = getThreadInfo();
326         Assert.isNotNull(info, "Flush requested outside of resource lock"); //$NON-NLS-1$
327
info.flush(monitor);
328     }
329
330     public boolean isWithinActiveOperationScope(IResource resource) {
331         synchronized (infos) {
332             return getThreadInfo(resource) != null;
333         }
334     }
335 }
336
Popular Tags