KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jface > text > reconciler > AbstractReconciler


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.jface.text.reconciler;
12
13
14 import org.eclipse.core.runtime.Assert;
15 import org.eclipse.core.runtime.IProgressMonitor;
16
17 import org.eclipse.jface.text.DocumentEvent;
18 import org.eclipse.jface.text.IDocument;
19 import org.eclipse.jface.text.IDocumentListener;
20 import org.eclipse.jface.text.ITextInputListener;
21 import org.eclipse.jface.text.ITextViewer;
22
23
24 /**
25  * Abstract implementation of {@link IReconciler}. The reconciler
26  * listens to input document changes as well as changes of
27  * the input document of the text viewer it is installed on. Depending on
28  * its configuration it manages the received change notifications in a
29  * queue folding neighboring or overlapping changes together. The reconciler
30  * processes the dirty regions as a background activity after having waited for further
31  * changes for the configured duration of time. A reconciler is started using the
32  * {@link #install(ITextViewer)} method. As a first step {@link #initialProcess()} is
33  * executed in the background. Then, the reconciling thread waits for changes that
34  * need to be reconciled. A reconciler can be resumed by calling {@link #forceReconciling()}
35  * independent from the existence of actual changes. This mechanism is for subclasses only.
36  * It is the clients responsibility to stop a reconciler using its {@link #uninstall()}
37  * method. Unstopped reconcilers do not free their resources.
38  * <p>
39  * It is subclass responsibility to specify how dirty regions are processed.
40  * </p>
41  *
42  * @see org.eclipse.jface.text.IDocumentListener
43  * @see org.eclipse.jface.text.ITextInputListener
44  * @see org.eclipse.jface.text.reconciler.DirtyRegion
45  * @since 2.0
46  */

47 abstract public class AbstractReconciler implements IReconciler {
48
49
50     /**
51      * Background thread for the reconciling activity.
52      */

53     class BackgroundThread extends Thread JavaDoc {
54
55         /** Has the reconciler been canceled. */
56         private boolean fCanceled= false;
57         /** Has the reconciler been reset. */
58         private boolean fReset= false;
59         /** Some changes need to be processed. */
60         private boolean fIsDirty= false;
61         /** Is a reconciling strategy active. */
62         private boolean fIsActive= false;
63
64         /**
65          * Creates a new background thread. The thread
66          * runs with minimal priority.
67          *
68          * @param name the thread's name
69          */

70         public BackgroundThread(String JavaDoc name) {
71             super(name);
72             setPriority(Thread.MIN_PRIORITY);
73             setDaemon(true);
74         }
75
76         /**
77          * Returns whether a reconciling strategy is active right now.
78          *
79          * @return <code>true</code> if a activity is active
80          */

81         public boolean isActive() {
82             return fIsActive;
83         }
84
85         /**
86          * Returns whether some changes need to be processed.
87          *
88          * @return <code>true</code> if changes wait to be processed
89          * @since 3.0
90          */

91         public synchronized boolean isDirty() {
92             return fIsDirty;
93         }
94
95         /**
96          * Cancels the background thread.
97          */

98         public void cancel() {
99             fCanceled= true;
100             IProgressMonitor pm= fProgressMonitor;
101             if (pm != null)
102                 pm.setCanceled(true);
103             synchronized (fDirtyRegionQueue) {
104                 fDirtyRegionQueue.notifyAll();
105             }
106         }
107
108         /**
109          * Suspends the caller of this method until this background thread has
110          * emptied the dirty region queue.
111          */

112         public void suspendCallerWhileDirty() {
113             boolean isDirty;
114             do {
115                 synchronized (fDirtyRegionQueue) {
116                     isDirty= fDirtyRegionQueue.getSize() > 0;
117                     if (isDirty) {
118                         try {
119                             fDirtyRegionQueue.wait();
120                         } catch (InterruptedException JavaDoc x) {
121                         }
122                     }
123                 }
124             } while (isDirty);
125         }
126
127         /**
128          * Reset the background thread as the text viewer has been changed,
129          */

130         public void reset() {
131
132             if (fDelay > 0) {
133
134                 synchronized (this) {
135                     fIsDirty= true;
136                     fReset= true;
137                 }
138
139             } else {
140
141                 synchronized (this) {
142                     fIsDirty= true;
143                 }
144
145                 synchronized (fDirtyRegionQueue) {
146                     fDirtyRegionQueue.notifyAll();
147                 }
148             }
149
150             reconcilerReset();
151         }
152
153         /**
154          * The background activity. Waits until there is something in the
155          * queue managing the changes that have been applied to the text viewer.
156          * Removes the first change from the queue and process it.
157          * <p>
158          * Calls {@link AbstractReconciler#initialProcess()} on entrance.
159          * </p>
160          */

161         public void run() {
162
163             synchronized (fDirtyRegionQueue) {
164                 try {
165                     fDirtyRegionQueue.wait(fDelay);
166                 } catch (InterruptedException JavaDoc x) {
167                 }
168             }
169
170             initialProcess();
171
172             while (!fCanceled) {
173
174                 synchronized (fDirtyRegionQueue) {
175                     try {
176                         fDirtyRegionQueue.wait(fDelay);
177                     } catch (InterruptedException JavaDoc x) {
178                     }
179                 }
180
181                 if (fCanceled)
182                     break;
183
184                 if (!isDirty())
185                     continue;
186
187                 synchronized (this) {
188                     if (fReset) {
189                         fReset= false;
190                         continue;
191                     }
192                 }
193
194                 DirtyRegion r= null;
195                 synchronized (fDirtyRegionQueue) {
196                     r= fDirtyRegionQueue.removeNextDirtyRegion();
197                 }
198
199                 fIsActive= true;
200
201                 if (fProgressMonitor != null)
202                     fProgressMonitor.setCanceled(false);
203
204                 process(r);
205
206                 synchronized (fDirtyRegionQueue) {
207                     if (0 == fDirtyRegionQueue.getSize()) {
208                         synchronized (this) {
209                             fIsDirty= fProgressMonitor != null ? fProgressMonitor.isCanceled() : false;
210                         }
211                         fDirtyRegionQueue.notifyAll();
212                     }
213                 }
214
215                 fIsActive= false;
216             }
217         }
218     }
219
220     /**
221      * Internal document listener and text input listener.
222      */

223     class Listener implements IDocumentListener, ITextInputListener {
224
225         /*
226          * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
227          */

228         public void documentAboutToBeChanged(DocumentEvent e) {
229         }
230
231         /*
232          * @see IDocumentListener#documentChanged(DocumentEvent)
233          */

234         public void documentChanged(DocumentEvent e) {
235
236             if (!fThread.isDirty() && fThread.isAlive()) {
237                 if (!fIsAllowedToModifyDocument && Thread.currentThread() == fThread)
238                     throw new UnsupportedOperationException JavaDoc("The reconciler thread is not allowed to modify the document"); //$NON-NLS-1$
239
aboutToBeReconciled();
240             }
241
242             /*
243              * The second OR condition handles the case when the document
244              * gets changed while still inside initialProcess().
245              */

246             if (fProgressMonitor != null && (fThread.isActive() || fThread.isDirty() && fThread.isAlive()))
247                 fProgressMonitor.setCanceled(true);
248
249             if (fIsIncrementalReconciler)
250                 createDirtyRegion(e);
251
252             fThread.reset();
253
254         }
255
256         /*
257          * @see ITextInputListener#inputDocumentAboutToBeChanged(IDocument, IDocument)
258          */

259         public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
260
261             if (oldInput == fDocument) {
262
263                 if (fDocument != null)
264                     fDocument.removeDocumentListener(this);
265
266                 if (fIsIncrementalReconciler) {
267                     synchronized (fDirtyRegionQueue) {
268                         fDirtyRegionQueue.purgeQueue();
269                     }
270                     if (fDocument != null && fDocument.getLength() > 0) {
271                         DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), ""); //$NON-NLS-1$
272
createDirtyRegion(e);
273                         fThread.reset();
274                         fThread.suspendCallerWhileDirty();
275                     }
276                 }
277
278                 fDocument= null;
279             }
280         }
281
282         /*
283          * @see ITextInputListener#inputDocumentChanged(IDocument, IDocument)
284          */

285         public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
286
287             fDocument= newInput;
288             if (fDocument == null)
289                 return;
290
291
292             reconcilerDocumentChanged(fDocument);
293
294             fDocument.addDocumentListener(this);
295
296             if (!fThread.isDirty())
297                 aboutToBeReconciled();
298
299             if (fIsIncrementalReconciler) {
300                 DocumentEvent e= new DocumentEvent(fDocument, 0, 0, fDocument.get());
301                 createDirtyRegion(e);
302             }
303
304             startReconciling();
305         }
306     }
307
308     /** Queue to manage the changes applied to the text viewer. */
309     private DirtyRegionQueue fDirtyRegionQueue;
310     /** The background thread. */
311     private BackgroundThread fThread;
312     /** Internal document and text input listener. */
313     private Listener fListener;
314     /** The background thread delay. */
315     private int fDelay= 500;
316     /** Are there incremental reconciling strategies? */
317     private boolean fIsIncrementalReconciler= true;
318     /** The progress monitor used by this reconciler. */
319     private IProgressMonitor fProgressMonitor;
320     /**
321      * Tells whether this reconciler is allowed to modify the document.
322      * @since 3.2
323      */

324     private boolean fIsAllowedToModifyDocument= true;
325
326
327     /** The text viewer's document. */
328     private IDocument fDocument;
329     /** The text viewer */
330     private ITextViewer fViewer;
331
332
333     /**
334      * Processes a dirty region. If the dirty region is <code>null</code> the whole
335      * document is consider being dirty. The dirty region is partitioned by the
336      * document and each partition is handed over to a reconciling strategy registered
337      * for the partition's content type.
338      *
339      * @param dirtyRegion the dirty region to be processed
340      */

341     abstract protected void process(DirtyRegion dirtyRegion);
342
343     /**
344      * Hook called when the document whose contents should be reconciled
345      * has been changed, i.e., the input document of the text viewer this
346      * reconciler is installed on. Usually, subclasses use this hook to
347      * inform all their reconciling strategies about the change.
348      *
349      * @param newDocument the new reconciler document
350      */

351     abstract protected void reconcilerDocumentChanged(IDocument newDocument);
352
353
354     /**
355      * Creates a new reconciler without configuring it.
356      */

357     protected AbstractReconciler() {
358         super();
359     }
360
361     /**
362      * Tells the reconciler how long it should wait for further text changes before
363      * activating the appropriate reconciling strategies.
364      *
365      * @param delay the duration in milliseconds of a change collection period.
366      */

367     public void setDelay(int delay) {
368         fDelay= delay;
369     }
370
371     /**
372      * Tells the reconciler whether any of the available reconciling strategies
373      * is interested in getting detailed dirty region information or just in the
374      * fact that the document has been changed. In the second case, the reconciling
375      * can not incrementally be pursued.
376      *
377      * @param isIncremental indicates whether this reconciler will be configured with
378      * incremental reconciling strategies
379      *
380      * @see DirtyRegion
381      * @see IReconcilingStrategy
382      */

383     public void setIsIncrementalReconciler(boolean isIncremental) {
384         fIsIncrementalReconciler= isIncremental;
385     }
386     
387     /**
388      * Tells the reconciler whether it is allowed to change the document
389      * inside its reconciler thread.
390      * <p>
391      * If this is set to <code>false</code> an {@link UnsupportedOperationException}
392      * will be thrown when this restriction will be violated.
393      * </p>
394      *
395      * @param isAllowedToModify indicates whether this reconciler is allowed to modify the document
396      * @since 3.2
397      */

398     public void setIsAllowedToModifyDocument(boolean isAllowedToModify) {
399         fIsAllowedToModifyDocument= isAllowedToModify;
400     }
401
402     /**
403      * Sets the progress monitor of this reconciler.
404      *
405      * @param monitor the monitor to be used
406      */

407     public void setProgressMonitor(IProgressMonitor monitor) {
408         fProgressMonitor= monitor;
409     }
410
411     /**
412      * Returns whether any of the reconciling strategies is interested in
413      * detailed dirty region information.
414      *
415      * @return whether this reconciler is incremental
416      *
417      * @see IReconcilingStrategy
418      */

419     protected boolean isIncrementalReconciler() {
420         return fIsIncrementalReconciler;
421     }
422
423     /**
424      * Returns the input document of the text viewer this reconciler is installed on.
425      *
426      * @return the reconciler document
427      */

428     protected IDocument getDocument() {
429         return fDocument;
430     }
431
432     /**
433      * Returns the text viewer this reconciler is installed on.
434      *
435      * @return the text viewer this reconciler is installed on
436      */

437     protected ITextViewer getTextViewer() {
438         return fViewer;
439     }
440
441     /**
442      * Returns the progress monitor of this reconciler.
443      *
444      * @return the progress monitor of this reconciler
445      */

446     protected IProgressMonitor getProgressMonitor() {
447         return fProgressMonitor;
448     }
449
450     /*
451      * @see IReconciler#install(ITextViewer)
452      */

453     public void install(ITextViewer textViewer) {
454
455         Assert.isNotNull(textViewer);
456         fViewer= textViewer;
457
458         synchronized (this) {
459             if (fThread != null)
460                 return;
461             fThread= new BackgroundThread(getClass().getName());
462         }
463
464         fDirtyRegionQueue= new DirtyRegionQueue();
465
466         fListener= new Listener();
467         fViewer.addTextInputListener(fListener);
468
469         // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=67046
470
// if the reconciler gets installed on a viewer that already has a document
471
// (e.g. when reusing editors), we force the listener to register
472
// itself as document listener, because there will be no input change
473
// on the viewer.
474
// In order to do that, we simulate an input change.
475
IDocument document= textViewer.getDocument();
476         if (document != null) {
477             fListener.inputDocumentAboutToBeChanged(fDocument, document);
478             fListener.inputDocumentChanged(fDocument, document);
479         }
480     }
481
482     /*
483      * @see IReconciler#uninstall()
484      */

485     public void uninstall() {
486         if (fListener != null) {
487
488             fViewer.removeTextInputListener(fListener);
489             if (fDocument != null) {
490                 fListener.inputDocumentAboutToBeChanged(fDocument, null);
491                 fListener.inputDocumentChanged(fDocument, null);
492             }
493             fListener= null;
494
495             synchronized (this) {
496                 // http://dev.eclipse.org/bugs/show_bug.cgi?id=19135
497
BackgroundThread bt= fThread;
498                 fThread= null;
499                 bt.cancel();
500             }
501         }
502     }
503
504     /**
505      * Creates a dirty region for a document event and adds it to the queue.
506      *
507      * @param e the document event for which to create a dirty region
508      */

509     private void createDirtyRegion(DocumentEvent e) {
510         synchronized (fDirtyRegionQueue) {
511             if (e.getLength() == 0 && e.getText() != null) {
512                 // Insert
513
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText()));
514     
515             } else if (e.getText() == null || e.getText().length() == 0) {
516                 // Remove
517
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null));
518     
519             } else {
520                 // Replace (Remove + Insert)
521
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null));
522                 fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText()));
523             }
524         }
525     }
526
527     /**
528      * Hook for subclasses which want to perform some
529      * action as soon as reconciliation is needed.
530      * <p>
531      * Default implementation is to do nothing.
532      * </p>
533      *
534      * @since 3.0
535      */

536     protected void aboutToBeReconciled() {
537     }
538
539     /**
540      * This method is called on startup of the background activity. It is called only
541      * once during the life time of the reconciler. Clients may reimplement this method.
542      */

543     protected void initialProcess() {
544     }
545
546     /**
547      * Forces the reconciler to reconcile the structure of the whole document.
548      * Clients may extend this method.
549      */

550     protected void forceReconciling() {
551
552         if (fDocument != null) {
553
554             if (!fThread.isDirty()&& fThread.isAlive())
555                 aboutToBeReconciled();
556
557             if (fProgressMonitor != null && fThread.isActive())
558                 fProgressMonitor.setCanceled(true);
559             
560             if (fIsIncrementalReconciler) {
561                 DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), fDocument.get());
562                 createDirtyRegion(e);
563             }
564
565             startReconciling();
566         }
567     }
568
569     /**
570      * Starts the reconciler to reconcile the queued dirty-regions.
571      * Clients may extend this method.
572      */

573     protected synchronized void startReconciling() {
574         if (fThread == null)
575             return;
576
577         if (!fThread.isAlive()) {
578             try {
579                 fThread.start();
580             } catch (IllegalThreadStateException JavaDoc e) {
581                 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=40549
582
// This is the only instance where the thread is started; since
583
// we checked that it is not alive, it must be dead already due
584
// to a run-time exception or error. Exit.
585
}
586         } else {
587             fThread.reset();
588         }
589     }
590
591     /**
592      * Hook that is called after the reconciler thread has been reset.
593      */

594     protected void reconcilerReset() {
595     }
596 }
597
Popular Tags