KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > internal > core > BackgroundEventHandler


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;
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.IWorkspaceRunnable;
18 import org.eclipse.core.runtime.*;
19 import org.eclipse.core.runtime.jobs.*;
20 import org.eclipse.team.core.TeamException;
21
22 /**
23  * This class provides the infrastructure for processing/dispatching of events using a
24  * background job. This is useful in the following situations.
25  * <ul>
26  * <li>an operation is potentially long running but a responsive UI is desired
27  * while the operation is being performed. To do this incoming events are processed
28  * and resulting outgoing events are queued and then dispatched at an appropriate time,
29  * thus batching UI updates.</li>
30  * <li>a change is a POST_CHANGE delta requires further modifications to the workspace
31  * which cannot be performed in the delta handler because the workspace is locked.</li>
32  * <li>a data structure is not thread safe and requires serialized operations.<li>
33  * </ul>
34  * </p>
35  * <p>
36  * The event handler has the following characteristics:
37  * <ol>
38  * <li>Incoming events are placed in an incoming queue.</li>
39  * <li>Each event is processed by calling the <code>processEvent</code> method
40  * which is implemented by the subclass. The implementation may choose to process events
41  * directly or queue events on an outgoing event queue</li>
42  * <li>The <code>doDispatchEvents</code> method of the subclass is called at certain intervals
43  * to give the subclass a chance to dispatch the events in it's outgoing queue. The interval between
44  * the first 3 dispatches will be the <code>shortDispatchDelay</code> and subsequent intervals will be
45  * the <code>longDispatchDelay</code>. This is done to avoid constantly hammering the UI for long running
46  * operations.<li>
47  * <li>Errors that occur during event processing or dispatch can be accumulated by calling the <code>handle</code>
48  * method. Accumulated errors are used to form the status that is returned when the job completes.<li>
49  * </ul>
50  * </p>
51  *
52  * @since 3.0
53  */

54 public abstract class BackgroundEventHandler {
55     
56     /**
57      * Event type constant used to identify a runnable event
58      */

59     public static final int RUNNABLE_EVENT = 1000;
60     
61     // Events that need to be processed
62
private List JavaDoc awaitingProcessing = new ArrayList JavaDoc();
63     
64     // The job that runs when events need to be processed
65
private Job eventHandlerJob;
66     
67     // Indicate if the event handler has been shutdown
68
private boolean shutdown;
69
70     // Accumulate exceptions that occur
71
private ExceptionCollector errors;
72     
73     // time the last dispatch occurred
74
private long timeOfLastDispatch = 0L;
75     
76     // the number of dispatches that have occurred since the job started
77
private int dispatchCount;
78
79     // time between event dispatches
80
private static final long DISPATCH_DELAY = 1500;
81     
82     // time between dispatches if the dispatch threshold has been exceeded
83
private static final long LONG_DISPATCH_DELAY = 10000;
84     
85     // the number of dispatches that can occur before using the long delay
86
private static final int DISPATCH_THRESHOLD = 3;
87     
88     // time to wait for messages to be queued
89
private static final long WAIT_DELAY = 100;
90
91     private String JavaDoc jobName;
92     
93     /**
94      * General event class. The type is specific to subclasses.
95      */

96     public static class Event {
97         private int type;
98         public Event(int type) {
99             this.type = type;
100         }
101         public int getType() {
102             return type;
103         }
104         public String JavaDoc toString() {
105             StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
106             buffer.append("Background Event: "); //$NON-NLS-1$
107
buffer.append(getTypeString());
108             return buffer.toString();
109         }
110         public IResource getResource() {
111             return null;
112         }
113         protected String JavaDoc getTypeString() {
114             return String.valueOf(type);
115         }
116     }
117     
118     /**
119      * Resource event class. The type is specific to subclasses.
120      */

121     public static class ResourceEvent extends Event {
122         private IResource resource;
123         private int depth;
124         public ResourceEvent(IResource resource, int type, int depth) {
125             super(type);
126             this.resource = resource;
127             this.depth = depth;
128         }
129         public int getDepth() {
130             return depth;
131         }
132         public IResource getResource() {
133             return resource;
134         }
135         public String JavaDoc toString() {
136             StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
137             buffer.append("resource: "); //$NON-NLS-1$
138
buffer.append(resource.getFullPath());
139             buffer.append(" type: "); //$NON-NLS-1$
140
buffer.append(getTypeString());
141             buffer.append(" depth: "); //$NON-NLS-1$
142
buffer.append(getDepthString());
143             return buffer.toString();
144         }
145         protected String JavaDoc getDepthString() {
146             switch (depth) {
147                 case IResource.DEPTH_ZERO :
148                     return "DEPTH_ZERO"; //$NON-NLS-1$
149
case IResource.DEPTH_ONE :
150                     return "DEPTH_ONE"; //$NON-NLS-1$
151
case IResource.DEPTH_INFINITE :
152                     return "DEPTH_INFINITE"; //$NON-NLS-1$
153
default :
154                     return "INVALID"; //$NON-NLS-1$
155
}
156         }
157     }
158     
159     /**
160      * This is a special event used to run some work in the background.
161      * The preemptive flag is used to indicate that the runnable should take
162      * the highest priority and thus be placed on the front of the queue
163      * and be processed as soon as possible, preempting any event that is currently
164      * being processed. The current event will continue processing once the
165      * high priority event has been processed
166      */

167     public static class RunnableEvent extends Event {
168         private IWorkspaceRunnable runnable;
169         private boolean preemtive;
170         public RunnableEvent(IWorkspaceRunnable runnable, boolean preemtive) {
171             super(RUNNABLE_EVENT);
172             this.runnable = runnable;
173             this.preemtive = preemtive;
174         }
175         public void run(IProgressMonitor monitor) throws CoreException {
176             runnable.run(monitor);
177         }
178         public boolean isPreemtive() {
179             return preemtive;
180         }
181     }
182     
183     protected BackgroundEventHandler(String JavaDoc jobName, String JavaDoc errorTitle) {
184         this.jobName = jobName;
185         errors =
186             new ExceptionCollector(
187                 errorTitle,
188                 TeamPlugin.ID,
189                 IStatus.ERROR,
190                 null /* don't log */
191         );
192         createEventHandlingJob();
193         schedule();
194     }
195     
196     /**
197      * Create the job used for processing the events in the queue. The job stops working when
198      * the queue is empty.
199      */

200     protected void createEventHandlingJob() {
201         eventHandlerJob = new Job(getName()) {
202             public IStatus run(IProgressMonitor monitor) {
203                 return processEvents(monitor);
204             }
205             public boolean shouldRun() {
206                 return ! isQueueEmpty();
207             }
208             public boolean shouldSchedule() {
209                 return ! isQueueEmpty();
210             }
211             public boolean belongsTo(Object JavaDoc family) {
212                 return BackgroundEventHandler.this.belongsTo(family);
213             }
214         };
215         eventHandlerJob.addJobChangeListener(new JobChangeAdapter() {
216             public void done(IJobChangeEvent event) {
217                 jobDone(event);
218             }
219         });
220         eventHandlerJob.setSystem(true);
221         eventHandlerJob.setPriority(Job.SHORT);
222     }
223     
224     /**
225      * Return whether this background handler belongs to the given job family.
226      * @param family the job family
227      * @return whether this background handler belongs to the given job family.
228      * @see Job#belongsTo(Object)
229      */

230     protected boolean belongsTo(Object JavaDoc family) {
231         return getJobFamiliy() == family;
232     }
233
234     /**
235      * Return the family that the background job for this
236      * event handler belongs to.
237      * @return the family that the background job for this
238      * event handler belongs to
239      */

240     protected Object JavaDoc getJobFamiliy() {
241         return null;
242     }
243
244     /**
245      * This method is invoked when the processing job completes. The
246      * default behavior of the handler is to restart the job if the queue
247      * is no longer empty and to clear the queue if the handler was shut down.
248      */

249     protected void jobDone(IJobChangeEvent event) {
250         if (isShutdown()) {
251             // The handler has been shutdown. Clean up the queue.
252
synchronized(this) {
253                 awaitingProcessing.clear();
254             }
255         } else if (! isQueueEmpty()) {
256             // An event squeaked in as the job was finishing. Reschedule the job.
257
schedule();
258         }
259     }
260     
261     /**
262      * Schedule the job to process the events now.
263      */

264     protected void schedule() {
265         eventHandlerJob.schedule();
266     }
267     
268     /**
269      * Shutdown the event handler. Any events on the queue will be removed from the queue
270      * and will not be processed.
271      */

272     public void shutdown() {
273         shutdown = true;
274         eventHandlerJob.cancel();
275     }
276     
277     /**
278      * Returns whether the handle has been shutdown.
279      * @return Returns whether the handle has been shutdown.
280      */

281     public boolean isShutdown() {
282         return shutdown;
283     }
284     
285     /**
286      * Queue the event and start the job if it's not already doing work. If the job is
287      * already running then notify in case it was waiting.
288      * @param event the event to be queued
289      */

290     protected synchronized void queueEvent(Event event, boolean front) {
291         if (Policy.DEBUG_BACKGROUND_EVENTS) {
292             System.out.println("Event queued on " + getName() + ":" + event.toString()); //$NON-NLS-1$ //$NON-NLS-2$
293
}
294         if (front) {
295             awaitingProcessing.add(0, event);
296         } else {
297             awaitingProcessing.add(event);
298         }
299         if (!isShutdown() && eventHandlerJob != null) {
300             if(eventHandlerJob.getState() == Job.NONE) {
301                 schedule();
302             } else {
303                 notify();
304             }
305         }
306     }
307     
308     /**
309      * Return the name that is to be associated with the background job.
310      * @return the job name
311      */

312     protected String JavaDoc getName() {
313         return jobName;
314     }
315
316     /*
317      * Return the next event that has been queued, removing it from the queue.
318      * @return the next event in the queue
319      */

320     protected synchronized Event nextElement() {
321         if (isShutdown() || isQueueEmpty()) {
322             return null;
323         }
324         return (Event) awaitingProcessing.remove(0);
325     }
326     
327     protected synchronized Event peek() {
328         if (isShutdown() || isQueueEmpty()) {
329             return null;
330         }
331         return (Event) awaitingProcessing.get(0);
332     }
333     
334     /**
335      * Return whether there are unprocessed events on the event queue.
336      * @return whether there are unprocessed events on the queue
337      */

338     protected synchronized boolean isQueueEmpty() {
339         return awaitingProcessing.isEmpty();
340     }
341     
342     /**
343      * Process events from the events queue and dispatch results. This method does not
344      * directly check for or handle cancelation of the provided monitor. However,
345      * it does invoke <code>processEvent(Event)</code> which may check for and handle
346      * cancelation by shutting down the receiver.
347      * <p>
348      * The <code>isReadyForDispatch()</code> method is used in conjunction
349      * with the <code>dispatchEvents(IProgressMonitor)</code> to allow
350      * the output of the event handler to be batched in order to avoid
351      * fine grained UI updating.
352      * @param monitor a progress monitor
353      */

354     protected IStatus processEvents(IProgressMonitor monitor) {
355         errors.clear();
356         try {
357             // It's hard to know how much work is going to happen
358
// since the queue can grow. Use the current queue size as a hint to
359
// an infinite progress monitor
360
monitor.beginTask(null, IProgressMonitor.UNKNOWN);
361             IProgressMonitor subMonitor = Policy.infiniteSubMonitorFor(monitor, 90);
362             subMonitor.beginTask(null, 1024);
363
364             Event event;
365             timeOfLastDispatch = System.currentTimeMillis();
366             dispatchCount = 1;
367             while ((event = nextElement()) != null && ! isShutdown()) {
368                 try {
369                     processEvent(event, subMonitor);
370                     if (Policy.DEBUG_BACKGROUND_EVENTS) {
371                         System.out.println("Event processed on " + getName() + ":" + event.toString()); //$NON-NLS-1$ //$NON-NLS-2$
372
}
373                     if(isReadyForDispatch(true /*wait if queue is empty*/)) {
374                         dispatchEvents(Policy.subMonitorFor(subMonitor, 1));
375                     }
376                 } catch (CoreException e) {
377                     // handle exception but keep going
378
handleException(e);
379                 }
380             }
381         } finally {
382             monitor.done();
383         }
384         return errors.getStatus();
385     }
386
387     /**
388      * Dispatch any accumulated events by invoking <code>doDispatchEvents</code>
389      * and then rest the dispatch counters.
390      * @param monitor a progress monitor
391      * @throws TeamException
392      */

393     protected final void dispatchEvents(IProgressMonitor monitor) throws TeamException {
394         if (doDispatchEvents(monitor)) {
395             // something was dispatched so adjust dispatch count.
396
dispatchCount++;
397         }
398         timeOfLastDispatch = System.currentTimeMillis();
399     }
400
401     /**
402      * Notify clients of processed events. Return <code>true</code> if there
403      * was something to dispatch and false otherwise. This is used to help
404      * control the frequency of dispatches (e.g. if there is a lot of dispatching
405      * going on, the frequency of dispatches may be reduced.
406      * @param monitor a progress monitor
407      */

408     protected abstract boolean doDispatchEvents(IProgressMonitor monitor) throws TeamException;
409
410     /**
411      * Returns <code>true</code> if processed events should be dispatched and
412      * <code>false</code> otherwise. Events are dispatched at regular intervals
413      * to avoid fine grain events causing the UI to be too jumpy. Also, if the
414      * events queue is empty we will wait a small amount of time to allow
415      * pending events to be queued. The queueEvent notifies when events are
416      * queued.
417      * @return <code>true</code> if processed events should be dispatched and
418      * <code>false</code> otherwise
419      */

420     protected boolean isReadyForDispatch(boolean wait) {
421         // Check if the time since the last dispatch is greater than the delay.
422
if (isDispatchDelayExceeded())
423             return true;
424         
425         synchronized(this) {
426             // If we have incoming events, process them before dispatching
427
if(! isQueueEmpty() || ! wait) {
428                 return false;
429             }
430             // There are no incoming events but we want to wait a little before
431
// dispatching in case more events come in.
432
try {
433                 wait(getDispatchWaitDelay());
434             } catch (InterruptedException JavaDoc e) {
435                 // just continue
436
}
437         }
438         return isQueueEmpty() || isDispatchDelayExceeded();
439     }
440     
441     private boolean isDispatchDelayExceeded() {
442         long duration = System.currentTimeMillis() - timeOfLastDispatch;
443         return ((dispatchCount < DISPATCH_THRESHOLD && duration >= getShortDispatchDelay()) ||
444                 duration >= getLongDispatchDelay());
445     }
446
447     /**
448      * Return the amount of time to wait for more events before dispatching.
449      * @return the amount of time to wait for more events before dispatching.
450      */

451     protected long getDispatchWaitDelay() {
452         return WAIT_DELAY;
453     }
454
455     /**
456      * Return the value that is used to determine how often
457      * the events are dispatched (i.e. how often the UI is
458      * updated) for the first 3 cycles. The default value is 1.5 seconds.
459      * After the first 3 cycles, a longer delay is used
460      * @return the dispatch delay used for the first 3 cycles.
461      */

462     protected long getShortDispatchDelay() {
463         return DISPATCH_DELAY;
464     }
465     
466     /**
467      * Return the value that is used to determine how often
468      * the events are dispatched (i.e. how often the UI is
469      * updated) after the first 3 cycles. The default value is 10 seconds.
470      * @return the dispatch delay used after the first 3 cycles.
471      */

472     protected long getLongDispatchDelay() {
473         return LONG_DISPATCH_DELAY;
474     }
475
476     /**
477      * Handle the exception by recording it in the errors list.
478      * @param e
479      */

480     protected void handleException(CoreException e) {
481         errors.handleException(e);
482     }
483     
484     /**
485      * Process the event in the context of a running background job. Subclasses may
486      * (but are not required to) check the provided monitor for cancelation and shut down the
487      * receiver by invoking the <code>shutdown()</code> method.
488      * <p>
489      * In many cases, a background event handler will translate incoming events into outgoing
490      * events. If this is the case, the handler should accumulate these events in the
491      * <code>proceessEvent</code> method and propagate them from the <code>dispatchEvent</code>
492      * method which is invoked periodically in order to batch outgoing events and avoid
493      * the UI becoming too jumpy.
494      *
495      * @param event the <code>Event</code> to be processed
496      * @param monitor a progress monitor
497      */

498     protected abstract void processEvent(Event event, IProgressMonitor monitor) throws CoreException;
499
500     /**
501      * Return the job from which the <code>processedEvent</code> method is invoked.
502      * @return Returns the background event handling job.
503      */

504     public Job getEventHandlerJob() {
505         return eventHandlerJob;
506     }
507 }
508
Popular Tags