KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > internal > texteditor > quickdiff > DocumentLineDiffer


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.ui.internal.texteditor.quickdiff;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.ConcurrentModificationException JavaDoc;
15 import java.util.Iterator JavaDoc;
16 import java.util.LinkedList JavaDoc;
17 import java.util.List JavaDoc;
18 import java.util.ListIterator JavaDoc;
19
20 import org.eclipse.core.runtime.Assert;
21 import org.eclipse.core.runtime.CoreException;
22 import org.eclipse.core.runtime.IProgressMonitor;
23 import org.eclipse.core.runtime.IStatus;
24 import org.eclipse.core.runtime.OperationCanceledException;
25 import org.eclipse.core.runtime.Platform;
26 import org.eclipse.core.runtime.Status;
27 import org.eclipse.core.runtime.jobs.Job;
28
29 import org.eclipse.jface.text.BadLocationException;
30 import org.eclipse.jface.text.Document;
31 import org.eclipse.jface.text.DocumentEvent;
32 import org.eclipse.jface.text.DocumentRewriteSessionEvent;
33 import org.eclipse.jface.text.DocumentRewriteSessionType;
34 import org.eclipse.jface.text.IDocument;
35 import org.eclipse.jface.text.IDocumentExtension4;
36 import org.eclipse.jface.text.IDocumentListener;
37 import org.eclipse.jface.text.IDocumentRewriteSessionListener;
38 import org.eclipse.jface.text.IRegion;
39 import org.eclipse.jface.text.ISynchronizable;
40 import org.eclipse.jface.text.Position;
41 import org.eclipse.jface.text.source.Annotation;
42 import org.eclipse.jface.text.source.AnnotationModelEvent;
43 import org.eclipse.jface.text.source.IAnnotationModel;
44 import org.eclipse.jface.text.source.IAnnotationModelListener;
45 import org.eclipse.jface.text.source.IAnnotationModelListenerExtension;
46 import org.eclipse.jface.text.source.ILineDiffInfo;
47 import org.eclipse.jface.text.source.ILineDiffer;
48 import org.eclipse.jface.text.source.ILineDifferExtension;
49 import org.eclipse.jface.text.source.ILineDifferExtension2;
50 import org.eclipse.jface.text.source.ILineRange;
51 import org.eclipse.jface.text.source.LineRange;
52
53 import org.eclipse.ui.internal.texteditor.NLSUtility;
54 import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
55 import org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DJBHashFunction;
56 import org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DocEquivalenceComparator;
57 import org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DocumentEquivalenceClass;
58 import org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.IHashFunction;
59 import org.eclipse.ui.internal.texteditor.quickdiff.compare.rangedifferencer.IRangeComparator;
60 import org.eclipse.ui.internal.texteditor.quickdiff.compare.rangedifferencer.RangeDifference;
61 import org.eclipse.ui.internal.texteditor.quickdiff.compare.rangedifferencer.RangeDifferencer;
62 import org.eclipse.ui.progress.IProgressConstants;
63 import org.eclipse.ui.texteditor.quickdiff.IQuickDiffReferenceProvider;
64
65 /**
66  * Standard implementation of <code>ILineDiffer</code> as an incremental diff engine. A
67  * <code>DocumentLineDiffer</code> can be initialized to some start state. Once connected to a
68  * <code>IDocument</code> and a reference document has been set, changes reported via the
69  * <code>IDocumentListener</code> interface will be tracked and the incremental diff updated.
70  *
71  * <p>The diff state can be queried using the <code>ILineDiffer</code> interface.</p>
72  *
73  * <p>Since diff information is model information attached to a document, this class implements
74  * <code>IAnnotationModel</code> and can be attached to <code>IAnnotationModelExtension</code>s.</p>
75  *
76  * <p>This class is not supposed to be subclassed.</p>
77  *
78  * @since 3.0
79  */

80 public class DocumentLineDiffer implements ILineDiffer, IDocumentListener, IAnnotationModel, ILineDifferExtension, ILineDifferExtension2 {
81
82     /**
83      * Artificial line difference information indicating a change with an empty line as original text.
84      */

85     private static class LineChangeInfo implements ILineDiffInfo {
86
87         private static final String JavaDoc[] ORIGINAL_TEXT= new String JavaDoc[] { "\n" }; //$NON-NLS-1$
88

89         /*
90          * @see org.eclipse.jface.text.source.ILineDiffInfo#getRemovedLinesBelow()
91          */

92         public int getRemovedLinesBelow() {
93             return 0;
94         }
95
96         /*
97          * @see org.eclipse.jface.text.source.ILineDiffInfo#getRemovedLinesAbove()
98          */

99         public int getRemovedLinesAbove() {
100             return 0;
101         }
102
103         /*
104          * @see org.eclipse.jface.text.source.ILineDiffInfo#getChangeType()
105          */

106         public int getChangeType() {
107             return CHANGED;
108         }
109
110         /*
111          * @see org.eclipse.jface.text.source.ILineDiffInfo#hasChanges()
112          */

113         public boolean hasChanges() {
114             return true;
115         }
116
117         /*
118          * @see org.eclipse.jface.text.source.ILineDiffInfo#getOriginalText()
119          */

120         public String JavaDoc[] getOriginalText() {
121             return ORIGINAL_TEXT;
122         }
123     }
124
125     /** Tells whether this class is in debug mode. */
126     private static boolean DEBUG= "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.ui.workbench.texteditor/debug/DocumentLineDiffer")); //$NON-NLS-1$//$NON-NLS-2$
127

128     /** The delay after which the initialization job is triggered. */
129     private static final int INITIALIZE_DELAY= 500;
130
131     /** Suspended state */
132     private static final int SUSPENDED= 0;
133     /** Initializing state */
134     private static final int INITIALIZING= 1;
135     /** Synchronized state */
136     private static final int SYNCHRONIZED= 2;
137
138     /** This differ's state */
139     private int fState= SUSPENDED;
140     /** Artificial line difference information indicating a change with an empty line as original text. */
141     private final ILineDiffInfo fLineChangeInfo= new LineChangeInfo();
142
143     /** The provider for the reference document. */
144     IQuickDiffReferenceProvider fReferenceProvider;
145     /** The number of clients connected to this model. */
146     private int fOpenConnections;
147     /** The current document being tracked. */
148     private IDocument fLeftDocument;
149     /**
150      * The equivalence class of the left document.
151      * @since 3.2
152      */

153     private DocumentEquivalenceClass fLeftEquivalent;
154     /** The reference document. */
155     private IDocument fRightDocument;
156     /**
157      * The equivalence class of the right document.
158      * @since 3.2
159      */

160     private DocumentEquivalenceClass fRightEquivalent;
161     /**
162      * Flag to indicate whether a change has been made to the line table and any clients should
163      * update their presentation.
164      */

165     private boolean fUpdateNeeded;
166     /** The listeners on this annotation model. */
167     private List JavaDoc fAnnotationModelListeners= new ArrayList JavaDoc();
168     /** The job currently initializing the differ, or <code>null</code> if there is none. */
169     private Job fInitializationJob;
170     /** Stores <code>DocumentEvents</code> while an initialization is going on. */
171     private List JavaDoc fStoredEvents= new ArrayList JavaDoc();
172     /**
173      * The differences between <code>fLeftDocument</code> and <code>fRightDocument</code>.
174      * This is the model we work on.
175      */

176     private List JavaDoc fDifferences= new ArrayList JavaDoc();
177     /**
178      * The differences removed in one iteration. Stored to be able to send out differentiated
179      * annotation events.
180      */

181     private List JavaDoc fRemoved= new ArrayList JavaDoc();
182     /**
183      * The differences added in one iteration. Stored to be able to send out differentiated
184      * annotation events.
185      */

186     private List JavaDoc fAdded= new ArrayList JavaDoc();
187     /**
188      * The differences changed in one iteration. Stored to be able to send out differentiated
189      * annotation events.
190      */

191     private List JavaDoc fChanged= new ArrayList JavaDoc();
192     /** The first line affected by a document event. */
193     private int fFirstLine;
194     /** The number of lines affected by a document event. */
195     private int fNLines;
196     /** The most recent range difference returned in a getLineInfo call, so it can be recyled. */
197     private RangeDifference fLastDifference;
198     /**
199      * <code>true</code> if incoming document events should be ignored,
200      * <code>false</code> if not.
201      */

202     private boolean fIgnoreDocumentEvents= true;
203     /**
204      * The listener for document rewrite sessions.
205      * @since 3.2
206      */

207     private final IDocumentRewriteSessionListener fSessionListener= new IDocumentRewriteSessionListener() {
208         public void documentRewriteSessionChanged(DocumentRewriteSessionEvent event) {
209             if (event.getSession().getSessionType() == DocumentRewriteSessionType.UNRESTRICTED_SMALL)
210                 return;
211             if (DocumentRewriteSessionEvent.SESSION_START.equals(event.getChangeType()))
212                 suspend();
213             else if (DocumentRewriteSessionEvent.SESSION_STOP.equals(event.getChangeType()))
214                 resume();
215         }
216     };
217
218     private Thread JavaDoc fThread;
219     private DocumentEvent fLastUIEvent;
220
221
222     /**
223      * Creates a new differ.
224      */

225     public DocumentLineDiffer() {
226     }
227
228     /* ILineDiffer implementation */
229
230     /*
231      * @see org.eclipse.jface.text.source.ILineDiffer#getLineInfo(int)
232      */

233     public ILineDiffInfo getLineInfo(int line) {
234
235         if (isSuspended())
236             return fLineChangeInfo;
237
238         // try cache first / speeds up linear search
239
RangeDifference last= fLastDifference;
240         if (last != null && last.rightStart() <= line && last.rightEnd() > line)
241             return new DiffRegion(last, line - last.rightStart(), fDifferences, fLeftDocument);
242
243         fLastDifference= getRangeDifferenceForRightLine(line);
244         last= fLastDifference;
245         if (last != null)
246             return new DiffRegion(last, line - last.rightStart(), fDifferences, fLeftDocument);
247
248         return null;
249     }
250
251     /*
252      * @see org.eclipse.jface.text.source.ILineDiffer#revertLine(int)
253      */

254     public synchronized void revertLine(int line) throws BadLocationException {
255         if (!isInitialized())
256             throw new BadLocationException(QuickDiffMessages.quickdiff_nonsynchronized);
257
258         DiffRegion region= (DiffRegion) getLineInfo(line);
259         if (region == null || fRightDocument == null || fLeftDocument == null)
260             return;
261
262         RangeDifference diff= region.getDifference();
263         int rOffset= fRightDocument.getLineOffset(line);
264         int rLength= fRightDocument.getLineLength(line);
265         int leftLine= diff.leftStart() + region.getOffset();
266         String JavaDoc replacement;
267         if (leftLine >= diff.leftEnd()) // restoring a deleted line?
268
replacement= ""; //$NON-NLS-1$
269
else {
270             int lOffset= fLeftDocument.getLineOffset(leftLine);
271             int lLength= fLeftDocument.getLineLength(leftLine);
272             replacement= fLeftDocument.get(lOffset, lLength);
273         }
274         fRightDocument.replace(rOffset, rLength, replacement);
275     }
276
277     /*
278      * @see org.eclipse.jface.text.source.ILineDiffer#revertBlock(int)
279      */

280     public synchronized void revertBlock(int line) throws BadLocationException {
281         if (!isInitialized())
282             throw new BadLocationException(QuickDiffMessages.quickdiff_nonsynchronized);
283
284         DiffRegion region= (DiffRegion) getLineInfo(line);
285         if (region == null || fRightDocument == null || fLeftDocument == null)
286             return;
287
288         RangeDifference diff= region.getDifference();
289         int rOffset= fRightDocument.getLineOffset(diff.rightStart());
290         int rLength= fRightDocument.getLineOffset(diff.rightEnd() - 1) + fRightDocument.getLineLength(diff.rightEnd() - 1) - rOffset;
291         int lOffset= fLeftDocument.getLineOffset(diff.leftStart());
292         int lLength= fLeftDocument.getLineOffset(diff.leftEnd() - 1) + fLeftDocument.getLineLength(diff.leftEnd() - 1) - lOffset;
293         fRightDocument.replace(rOffset, rLength, fLeftDocument.get(lOffset, lLength));
294     }
295
296     /*
297      * @see org.eclipse.jface.text.source.ILineDiffer#revertSelection(int, int)
298      */

299     public synchronized void revertSelection(int line, int nLines) throws BadLocationException {
300         if (!isInitialized())
301             throw new BadLocationException(QuickDiffMessages.quickdiff_nonsynchronized);
302
303         if (fRightDocument == null || fLeftDocument == null)
304             return;
305
306         int rOffset= -1, rLength= -1, lOffset= -1, lLength= -1;
307         RangeDifference diff= null;
308         final List JavaDoc differences= fDifferences;
309         synchronized (differences) {
310             Iterator JavaDoc it= differences.iterator();
311
312             // get start
313
while (it.hasNext()) {
314                 diff= (RangeDifference) it.next();
315                 if (line < diff.rightEnd()) {
316                     rOffset= fRightDocument.getLineOffset(line);
317                     int leftLine= Math.min(diff.leftStart() + line - diff.rightStart(), diff.leftEnd() - 1);
318                     lOffset= fLeftDocument.getLineOffset(leftLine);
319                     break;
320                 }
321             }
322
323             if (rOffset == -1 || lOffset == -1)
324                 return;
325
326             // get end / length
327
int to= line + nLines - 1;
328             while (it.hasNext()) {
329                 diff= (RangeDifference) it.next();
330                 if (to < diff.rightEnd()) {
331                     int rEndOffset= fRightDocument.getLineOffset(to) + fRightDocument.getLineLength(to);
332                     rLength= rEndOffset - rOffset;
333                     int leftLine= Math.min(diff.leftStart() + to - diff.rightStart(), diff.leftEnd() - 1);
334                     int lEndOffset= fLeftDocument.getLineOffset(leftLine) + fLeftDocument.getLineLength(leftLine);
335                     lLength= lEndOffset - lOffset;
336                     break;
337                 }
338             }
339         }
340
341         if (rLength == -1 || lLength == -1)
342             return;
343
344         fRightDocument.replace(rOffset, rLength, fLeftDocument.get(lOffset, lLength));
345     }
346
347     /*
348      * @see org.eclipse.jface.text.source.ILineDiffer#restoreAfterLine(int)
349      */

350     public synchronized int restoreAfterLine(int line) throws BadLocationException {
351         if (!isInitialized())
352             throw new BadLocationException(QuickDiffMessages.quickdiff_nonsynchronized);
353
354         DiffRegion region= (DiffRegion) getLineInfo(line);
355         if (region == null || fRightDocument == null || fLeftDocument == null)
356             return 0;
357
358         if (region.getRemovedLinesBelow() < 1)
359             return 0;
360
361         RangeDifference diff= null;
362         final List JavaDoc differences= fDifferences;
363         synchronized (differences) {
364             for (Iterator JavaDoc it= differences.iterator(); it.hasNext();) {
365                 diff= (RangeDifference) it.next();
366                 if (line >= diff.rightStart() && line < diff.rightEnd()) {
367                     if (diff.kind() == RangeDifference.NOCHANGE && it.hasNext())
368                         diff= (RangeDifference) it.next();
369                     break;
370                 }
371             }
372         }
373
374         if (diff == null)
375             return 0;
376
377         int rOffset= fRightDocument.getLineOffset(diff.rightEnd());
378         int rLength= 0;
379         int leftLine= diff.leftStart() + diff.rightLength();
380         int lOffset= fLeftDocument.getLineOffset(leftLine);
381         int lLength= fLeftDocument.getLineOffset(diff.leftEnd() - 1) + fLeftDocument.getLineLength(diff.leftEnd() - 1) - lOffset;
382         fRightDocument.replace(rOffset, rLength, fLeftDocument.get(lOffset, lLength));
383
384         return diff.leftLength() - diff.rightLength();
385     }
386
387     /**
388      * Returns the receivers initialization state.
389      *
390      * @return <code>true</code> if we are initialized and in sync with the document.
391      */

392     private boolean isInitialized() {
393         return fState == SYNCHRONIZED;
394     }
395
396     /**
397      * Returns the receivers synchronization state.
398      *
399      * @return <code>true</code> if we are initialized and in sync with the document.
400      */

401     public synchronized boolean isSynchronized() {
402         return fState == SYNCHRONIZED;
403     }
404
405     /**
406      * Returns <code>true</code> if the differ is suspended.
407      *
408      * @return <code>true</code> if the differ is suspended
409      */

410     public synchronized boolean isSuspended() {
411         return fState == SUSPENDED;
412     }
413
414     /**
415      * Sets the reference provider for this instance. If one has been installed before, it is
416      * disposed.
417      *
418      * @param provider the new provider
419      */

420     public void setReferenceProvider(IQuickDiffReferenceProvider provider) {
421         Assert.isNotNull(provider);
422         if (provider != fReferenceProvider) {
423             if (fReferenceProvider != null)
424                 fReferenceProvider.dispose();
425             fReferenceProvider= provider;
426             initialize();
427         }
428     }
429
430     /**
431      * Returns the reference provider currently installed, or <code>null</code> if none is installed.
432      *
433      * @return the current reference provider.
434      */

435     public IQuickDiffReferenceProvider getReferenceProvider() {
436         return fReferenceProvider;
437     }
438
439     /**
440      * (Re-)initializes the differ using the current reference and <code>DiffInitializer</code>.
441      *
442      * @since 3.2 protected for testing reasons, package visible before
443      */

444     protected synchronized void initialize() {
445         // make new incoming changes go into the queue of stored events, plus signal we can't restore.
446
fState= INITIALIZING;
447
448         if (fRightDocument == null)
449             return;
450
451         // there is no point in receiving updates before the job we get a new copy of the document for diffing
452
fIgnoreDocumentEvents= true;
453
454         if (fLeftDocument != null) {
455             fLeftDocument.removeDocumentListener(this);
456             fLeftDocument= null;
457             fLeftEquivalent= null;
458         }
459
460         // if there already is a job:
461
// return if it has not started yet, cancel it if already running
462
final Job oldJob= fInitializationJob;
463         if (oldJob != null) {
464             // don't chain up jobs if there is one waiting already.
465
if (oldJob.getState() == Job.WAITING) {
466                 oldJob.wakeUp(INITIALIZE_DELAY);
467                 return;
468             }
469             oldJob.cancel();
470         }
471
472         fInitializationJob= new Job(QuickDiffMessages.quickdiff_initialize) {
473
474             /*
475              * This is run in a different thread. As the documents might be synchronized, never ever
476              * access the documents in a synchronized section or expect deadlocks. See
477              * https://bugs.eclipse.org/bugs/show_bug.cgi?id=44692
478              */

479             public IStatus run(IProgressMonitor monitor) {
480
481                 // 1: wait for any previous job that was canceled to avoid job flooding
482
// It will return relatively quickly as RangeDifferencer supports canceling
483
if (oldJob != null)
484                     try {
485                         oldJob.join();
486                     } catch (InterruptedException JavaDoc e) {
487                         // will not happen as no one interrupts our thread
488
Assert.isTrue(false);
489                     }
490
491
492                 // 2: get the reference document
493
IQuickDiffReferenceProvider provider= fReferenceProvider;
494                 final IDocument left;
495                 try {
496                     left= provider == null ? null : provider.getReference(monitor);
497                 } catch (CoreException e) {
498                     synchronized (DocumentLineDiffer.this) {
499                         if (isCanceled(monitor))
500                             return Status.CANCEL_STATUS;
501
502                         clearModel();
503                         fireModelChanged();
504                         return e.getStatus();
505                     }
506                 } catch (OperationCanceledException e) {
507                     return Status.CANCEL_STATUS;
508                 }
509
510                 // Getting our own copies of the documents for offline diffing.
511
//
512
// We need to make sure that we do get all document modifications after
513
// copying the documents as we want to re-inject them later on to become consistent.
514

515                 IDocument right= fRightDocument; // fRightDocument, but not subject to change
516
IDocument actual= null; // the copy of the actual (right) document
517
IDocument reference= null; // the copy of the reference (left) document
518

519                 synchronized (DocumentLineDiffer.this) {
520                     // 4: take an early exit if the documents are not valid
521
if (left == null || right == null) {
522                         if (isCanceled(monitor))
523                             return Status.CANCEL_STATUS;
524
525                         clearModel();
526                         fireModelChanged();
527                         return Status.OK_STATUS;
528                     }
529
530                     // set the reference document
531
fLeftDocument= left;
532                     // start listening to document events.
533
fIgnoreDocumentEvents= false;
534                 }
535
536                 // accessing the reference document from a different thread - reference providers need
537
// to be able to deal with this.
538
left.addDocumentListener(DocumentLineDiffer.this);
539                 
540                 // create the reference copy - note that any changes on the
541
// reference will trigger re-initialization anyway
542
reference= createCopy(left);
543                 if (reference == null)
544                     return Status.CANCEL_STATUS;
545                 
546                 // create the actual copy
547

548                 Object JavaDoc lock= null;
549                 if (right instanceof ISynchronizable)
550                     lock= ((ISynchronizable) right).getLockObject();
551                     
552                 if (lock != null) {
553                     // a) if we can, acquire locks in proper order and copy
554
// the document
555
synchronized (lock) {
556                         synchronized (DocumentLineDiffer.this) {
557                             if (isCanceled(monitor))
558                                 return Status.CANCEL_STATUS;
559                             fStoredEvents.clear();
560                             actual= createUnprotectedCopy(right);
561                         }
562                     }
563                 } else {
564                     // b) cannot lock the document
565
// Now this is fun. The reference documents may be PartiallySynchronizedDocuments
566
// which will result in a deadlock if they get changed externally before we get
567
// our exclusive copies.
568
// Here's what we do: we try over and over (without synchronization) to get copies
569
// without interleaving modification. If there is a document change, we just repeat.
570
int i= 0;
571                     do {
572                         // this is an arbitrary emergency exit in case a referenced document goes nuts
573
if (i++ == 100)
574                             return new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK, NLSUtility.format(QuickDiffMessages.quickdiff_error_getting_document_content, new Object JavaDoc[] {left.getClass(), right.getClass()}), null);
575                         
576                         synchronized (DocumentLineDiffer.this) {
577                             if (isCanceled(monitor))
578                                 return Status.CANCEL_STATUS;
579                             
580                             fStoredEvents.clear();
581                         }
582                         
583                         // access documents non synchronized:
584
// get an exclusive copy of the actual document
585
actual= createCopy(right);
586                         
587                         synchronized (DocumentLineDiffer.this) {
588                             if (isCanceled(monitor))
589                                 return Status.CANCEL_STATUS;
590                             if (fStoredEvents.size() == 0 && actual != null)
591                                 break;
592                         }
593                     } while (true);
594                 }
595
596                 IHashFunction hash= new DJBHashFunction();
597                 DocumentEquivalenceClass leftEquivalent= new DocumentEquivalenceClass(reference, hash);
598                 fLeftEquivalent= leftEquivalent;
599                 IRangeComparator ref= new DocEquivalenceComparator(leftEquivalent, null);
600                 
601                 DocumentEquivalenceClass rightEquivalent= new DocumentEquivalenceClass(actual, hash);
602                 fRightEquivalent= rightEquivalent;
603                 IRangeComparator act= new DocEquivalenceComparator(rightEquivalent, null);
604                 List JavaDoc diffs= RangeDifferencer.findRanges(monitor, ref, act);
605                 // 7: Reset the model to the just gotten differences
606
// re-inject stored events to get up to date.
607
synchronized (DocumentLineDiffer.this) {
608                     if (isCanceled(monitor))
609                         return Status.CANCEL_STATUS;
610
611                     // set the new differences so we can operate on them
612
fDifferences= diffs;
613                 }
614
615                 // re-inject events accumulated in the meantime.
616
try {
617                     do {
618                         DocumentEvent event;
619                         synchronized (DocumentLineDiffer.this) {
620                             if (isCanceled(monitor))
621                                 return Status.CANCEL_STATUS;
622
623                             if (fStoredEvents.isEmpty()) {
624                                 // we are back in sync with the life documents
625
fInitializationJob= null;
626                                 fState= SYNCHRONIZED;
627                                 fLastDifference= null;
628
629                                 // replace the private documents with the actual
630
leftEquivalent.setDocument(left);
631                                 rightEquivalent.setDocument(right);
632
633                                 break;
634                             }
635
636                             event= (DocumentEvent) fStoredEvents.remove(0);
637                         }
638                         
639                         // access documents non synchronized:
640
IDocument copy= null;
641                         if (event.fDocument == right)
642                             copy= actual;
643                         else if (event.fDocument == left)
644                             copy= reference;
645                         else
646                             Assert.isTrue(false);
647                         
648                         // copy the event to inject it into our diff copies
649
// don't modify the original event! See https://bugs.eclipse.org/bugs/show_bug.cgi?id=134227
650
event= new DocumentEvent(copy, event.fOffset, event.fLength, event.fText);
651                         handleAboutToBeChanged(event);
652                         
653                         // inject the event into our private copy
654
actual.replace(event.fOffset, event.fLength, event.fText);
655                         
656                         handleChanged(event);
657
658                     } while (true);
659
660                 } catch (BadLocationException e) {
661                     left.removeDocumentListener(DocumentLineDiffer.this);
662                     clearModel();
663                     initialize();
664                     return Status.CANCEL_STATUS;
665                 }
666
667                 fireModelChanged();
668                 return Status.OK_STATUS;
669             }
670
671             private boolean isCanceled(IProgressMonitor monitor) {
672                 return fInitializationJob != this || monitor != null && monitor.isCanceled();
673             }
674
675             private void clearModel() {
676                 synchronized (DocumentLineDiffer.this) {
677                     fLeftDocument= null;
678                     fLeftEquivalent= null;
679                     fInitializationJob= null;
680                     fStoredEvents.clear();
681                     fLastDifference= null;
682                     fDifferences.clear();
683                 }
684             }
685
686             /**
687              * Creates a copy of <code>document</code> and catches any
688              * exceptions that may occur if the document is modified concurrently.
689              * Only call this method in a synchronized block if the document is
690              * an ISynchronizable and has been locked, as document.get() is called
691              * and may result in a deadlock otherwise.
692              *
693              * @param document the document to create a copy of
694              * @return a copy of the document, or <code>null</code> if an exception was thrown
695              */

696             private IDocument createCopy(IDocument document) {
697                 Assert.isNotNull(document);
698                 // this fixes https://bugs.eclipse.org/bugs/show_bug.cgi?id=56091
699
try {
700                     return createUnprotectedCopy(document);
701                 } catch (NullPointerException JavaDoc e) {
702                 } catch (ArrayStoreException JavaDoc e) {
703                 } catch (IndexOutOfBoundsException JavaDoc e) {
704                 } catch (ConcurrentModificationException JavaDoc e) {
705                 } catch (NegativeArraySizeException JavaDoc e) {
706                 }
707                 return null;
708             }
709
710             private IDocument createUnprotectedCopy(IDocument document) {
711                 return new Document(document.get());
712             }
713         };
714
715         fInitializationJob.setSystem(true);
716         fInitializationJob.setPriority(Job.DECORATE);
717         fInitializationJob.setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);
718         fInitializationJob.schedule(INITIALIZE_DELAY);
719     }
720
721     /* IDocumentListener implementation */
722
723     /*
724      * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
725      */

726     public synchronized void documentAboutToBeChanged(DocumentEvent event) {
727         if (fIgnoreDocumentEvents)
728             return;
729
730         if (event.getDocument() == fLeftDocument) { // TODO twoside
731
initialize();
732             return;
733         }
734         
735         // if a initialization is going on, we just store the events in the meantime
736
if (!isInitialized()) {
737             if (fInitializationJob != null)
738                 fStoredEvents.add(event);
739             return;
740         }
741
742         fLastUIEvent= event;
743         try {
744             handleAboutToBeChanged(event);
745         } catch (BadLocationException e) {
746             reinitOnError(e);
747             return;
748         } catch (NullPointerException JavaDoc e) {
749             reinitOnError(e);
750             return;
751         } catch (ArrayStoreException JavaDoc e) {
752             reinitOnError(e);
753             return;
754         } catch (IndexOutOfBoundsException JavaDoc e) {
755             reinitOnError(e);
756             return;
757         } catch (ConcurrentModificationException JavaDoc e) {
758             reinitOnError(e);
759             return;
760         } catch (NegativeArraySizeException JavaDoc e) {
761             reinitOnError(e);
762             return;
763         }
764     }
765
766
767     /**
768      * Unsynchronized version of <code>documentAboutToBeChanged</code>, called by <code>documentAboutToBeChanged</code>
769      * and {@link #initialize()}.
770      *
771      * @param event the document event to be handled
772      * @throws BadLocationException if document access fails
773      */

774     void handleAboutToBeChanged(DocumentEvent event) throws BadLocationException {
775         Assert.isTrue(fThread == null);
776         fThread= Thread.currentThread();
777         
778         IDocument doc= event.getDocument();
779         DocumentEquivalenceClass rightEquivalent= fRightEquivalent;
780
781         if (doc == null || rightEquivalent == null)
782             return;
783
784         // store size of replaced region (never synchronized -> not a problem)
785
fFirstLine= doc.getLineOfOffset(event.getOffset()); // store change bounding lines
786
fNLines= doc.getLineOfOffset(event.getOffset() + event.getLength()) - fFirstLine + 1;
787         rightEquivalent.update(event);
788     }
789
790     /*
791      * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
792      */

793     public synchronized void documentChanged(DocumentEvent event) {
794         final Thread JavaDoc lastCurrentThread= fThread;
795         fThread= null;
796         
797         if (fIgnoreDocumentEvents)
798             return;
799
800         // https://bugs.eclipse.org/bugs/show_bug.cgi?id=44692
801
// don't allow incremental update for changes from the reference document
802
// as this could deadlock
803
if (event.getDocument() == fLeftDocument) { // TODO twoside
804
initialize();
805             return;
806         }
807         
808         if (event != fLastUIEvent) {
809             fLastUIEvent= null;
810             return;
811         }
812         fLastUIEvent= null;
813
814         if (!isInitialized())
815             return;
816         
817         try {
818             fThread= lastCurrentThread;
819             handleChanged(event);
820         } catch (BadLocationException e) {
821             reinitOnError(e);
822             return;
823         } catch (NullPointerException JavaDoc e) {
824             reinitOnError(e);
825             return;
826         } catch (ArrayStoreException JavaDoc e) {
827             reinitOnError(e);
828             return;
829         } catch (IndexOutOfBoundsException JavaDoc e) {
830             reinitOnError(e);
831             return;
832         } catch (ConcurrentModificationException JavaDoc e) {
833             reinitOnError(e);
834             return;
835         } catch (NegativeArraySizeException JavaDoc e) {
836             reinitOnError(e);
837             return;
838         }
839
840         // inform listeners about change
841
if (fUpdateNeeded) {
842             AnnotationModelEvent ame= new AnnotationModelEvent(this, false);
843             for (Iterator JavaDoc it= fAdded.iterator(); it.hasNext(); ) {
844                 RangeDifference rd= (RangeDifference) it.next();
845                 ame.annotationAdded(rd.getDiffRegion(fDifferences, fLeftDocument));
846             }
847             for (Iterator JavaDoc it= fRemoved.iterator(); it.hasNext(); ) {
848                 RangeDifference rd= (RangeDifference) it.next();
849                 ame.annotationRemoved(rd.getDiffRegion(fDifferences, fLeftDocument));
850             }
851             for (Iterator JavaDoc it= fChanged.iterator(); it.hasNext(); ) {
852                 RangeDifference rd= (RangeDifference) it.next();
853                 ame.annotationChanged(rd.getDiffRegion(fDifferences, fLeftDocument));
854             }
855             fireModelChanged(ame);
856             fUpdateNeeded= false;
857         }
858     }
859
860     /**
861      * Re-initializes the differ if an exception is thrown upon accessing the documents. This can
862      * happen if the documents get concurrently modified from a background thread.
863      *
864      * @param e the exception thrown, which is logged in debug mode
865      */

866     private void reinitOnError(Exception JavaDoc e) {
867         if (DEBUG)
868             System.err.println("reinitializing quickdiff:\n" + e.getLocalizedMessage() + "\n" + e.getStackTrace()); //$NON-NLS-1$//$NON-NLS-2$
869
initialize();
870     }
871
872     /**
873      * Implementation of documentChanged, non synchronized.
874      *
875      * @param event the document event
876      * @throws BadLocationException if document access fails somewhere
877      */

878     void handleChanged(DocumentEvent event) throws BadLocationException {
879         Assert.isTrue(fThread == Thread.currentThread());
880         fThread= null;
881         
882         // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=132125
883
IDocument left= fLeftDocument;
884         DocumentEquivalenceClass leftEquivalent= fLeftEquivalent;
885         DocumentEquivalenceClass rightEquivalent= fRightEquivalent;
886         if (left == null || leftEquivalent == null || rightEquivalent == null)
887             return;
888         
889         // documents: left, right; modified and unchanged are either of both
890
IDocument right= event.getDocument(); // TODO two-side
891
IDocument modified= event.getDocument();
892         if (modified != left && modified != right)
893             Assert.isTrue(false);
894         
895         boolean leftToRight= modified == left;
896
897         String JavaDoc insertion= event.getText();
898         int added= insertion == null ? 1 : modified.computeNumberOfLines(insertion) + 1;
899         // size: the size of the document change in lines
900

901         // put an upper bound to the delay we can afford
902
if (added > 50 || fNLines > 50) {
903             initialize();
904             return;
905         }
906
907         int size= Math.max(fNLines, added) + 1;
908         int lineDelta= added - fNLines;
909         int lastLine= fFirstLine + fNLines - 1;
910
911         int repetitionField;
912         if (leftToRight) {
913             int originalLine= getRightLine(lastLine + 1);
914             repetitionField= searchForRepetitionField(size - 1, right, originalLine);
915         } else {
916             int originalLine= getLeftLine(lastLine + 1);
917             repetitionField= searchForRepetitionField(size - 1, left, originalLine);
918         }
919         lastLine += repetitionField;
920
921
922         // get enclosing range: search for a consistent block of at least the size of our
923
// change before and after the change.
924
final RangeDifference consistentBefore, consistentAfter;
925         if (leftToRight) {
926             consistentBefore= findConsistentRangeBeforeLeft(fFirstLine, size);
927             consistentAfter= findConsistentRangeAfterLeft(lastLine, size);
928         } else {
929             consistentBefore= findConsistentRangeBeforeRight(fFirstLine, size);
930             consistentAfter= findConsistentRangeAfterRight(lastLine, size);
931         }
932
933         // optimize unchanged blocks: if the consistent blocks around the change are larger than
934
// size, we redimension them (especially important when there are only few changes.
935
int shiftBefore= 0;
936         if (consistentBefore.kind() == RangeDifference.NOCHANGE) {
937             int unchanged;
938             if (leftToRight)
939                 unchanged= Math.min(fFirstLine, consistentBefore.leftEnd()) - consistentBefore.leftStart();
940             else
941                 unchanged= Math.min(fFirstLine, consistentBefore.rightEnd()) - consistentBefore.rightStart();
942
943             shiftBefore= Math.max(0, unchanged - size);
944         }
945
946         int shiftAfter= 0;
947         if (consistentAfter.kind() == RangeDifference.NOCHANGE) {
948             int unchanged;
949             if (leftToRight)
950                 unchanged= consistentAfter.leftEnd() - Math.max(lastLine + 1, consistentAfter.leftStart());
951             else
952                 unchanged= consistentAfter.rightEnd() - Math.max(lastLine + 1, consistentAfter.rightStart());
953
954             shiftAfter= Math.max(0, unchanged - size);
955         }
956
957         // get the document regions that will be rediffed, take into account that on the
958
// document, the change has already happened.
959
// left (reference) document
960
int leftStartLine= consistentBefore.leftStart() + shiftBefore;
961         int leftLine= consistentAfter.leftEnd();
962         if (leftToRight)
963             leftLine += lineDelta;
964         int leftEndLine= leftLine - shiftAfter;
965         ILineRange leftRange= new LineRange(leftStartLine, leftEndLine - leftStartLine);
966         IRangeComparator reference= new DocEquivalenceComparator(leftEquivalent, leftRange);
967
968         // right (actual) document
969
int rightStartLine= consistentBefore.rightStart() + shiftBefore;
970         int rightLine= consistentAfter.rightEnd();
971         if (!leftToRight)
972             rightLine += lineDelta;
973         int rightEndLine= rightLine - shiftAfter;
974         ILineRange rightRange= new LineRange(rightStartLine, rightEndLine - rightStartLine);
975         IRangeComparator change= new DocEquivalenceComparator(rightEquivalent, rightRange);
976
977         // put an upper bound to the delay we can afford
978
if (leftLine - shiftAfter - leftStartLine > 50 || rightLine - shiftAfter - rightStartLine > 50) {
979             initialize();
980             return;
981         }
982
983         // debug
984
// System.out.println("compare window: "+size+"\n\n<" + left.get(leftRegion.getOffset(), leftRegion.getLength()) + //$NON-NLS-1$//$NON-NLS-2$
985
// ">\n\n<" + right.get(rightRegion.getOffset(), rightRegion.getLength()) + ">\n"); //$NON-NLS-1$ //$NON-NLS-2$
986

987         // compare
988
List JavaDoc diffs= RangeDifferencer.findRanges(reference, change);
989         if (diffs.size() == 0) {
990             diffs.add(new RangeDifference(RangeDifference.CHANGE, 0, 0, 0, 0));
991         }
992
993
994         // shift the partial diffs to the absolute document positions
995
for (Iterator JavaDoc it= diffs.iterator(); it.hasNext();) {
996             RangeDifference d= (RangeDifference) it.next();
997             d.shiftLeft(leftStartLine);
998             d.shiftRight(rightStartLine);
999         }
1000
1001        // undo optimization shifting
1002
if (shiftBefore > 0) {
1003            RangeDifference first= (RangeDifference) diffs.get(0);
1004            if (first.kind() == RangeDifference.NOCHANGE)
1005                first.extendStart(-shiftBefore);
1006            else
1007                diffs.add(0, new RangeDifference(RangeDifference.NOCHANGE, first.rightStart() - shiftBefore, shiftBefore, first.leftStart() - shiftBefore, shiftBefore));
1008        }
1009
1010        RangeDifference last= (RangeDifference) diffs.get(diffs.size() - 1);
1011        if (shiftAfter > 0) {
1012            if (last.kind() == RangeDifference.NOCHANGE)
1013                last.extendEnd(shiftAfter);
1014            else
1015                diffs.add(new RangeDifference(RangeDifference.NOCHANGE, last.rightEnd(), shiftAfter , last.leftEnd(), shiftAfter));
1016        }
1017
1018        // replace changed diff range
1019
synchronized (fDifferences) {
1020            final ListIterator JavaDoc it= fDifferences.listIterator();
1021            Iterator JavaDoc newIt= diffs.iterator();
1022            RangeDifference current;
1023            boolean changed= false;
1024
1025            // replace regions from consistentBefore to consistentAfter with new diffs
1026

1027            // search for consistentBefore
1028
do {
1029                Assert.isTrue(it.hasNext());
1030                current= (RangeDifference) it.next();
1031            } while (current != consistentBefore);
1032            Assert.isTrue(current == consistentBefore);
1033
1034            fChanged.clear();
1035            fRemoved.clear();
1036            fAdded.clear();
1037
1038            // replace until consistentAfter
1039
while (current != consistentAfter) {
1040                if (newIt.hasNext()) {
1041                    Object JavaDoc o= newIt.next();
1042                    if (!current.equals(o)) {
1043                        fRemoved.add(current);
1044                        fAdded.add(o);
1045                        changed= true;
1046                        it.set(o);
1047                    }
1048                } else {
1049                    fRemoved.add(current);
1050                    it.remove();
1051                    changed= true;
1052                }
1053                Assert.isTrue(it.hasNext());
1054                current= (RangeDifference) it.next();
1055            }
1056
1057            // replace consistentAfter
1058
Assert.isTrue(current == consistentAfter);
1059            if (newIt.hasNext()) {
1060                Object JavaDoc o= newIt.next();
1061                if (!current.equals(o)) {
1062                    fRemoved.add(current);
1063                    fAdded.add(o);
1064                    changed= true;
1065                    it.set(o);
1066                }
1067            } else {
1068                fRemoved.add(current);
1069                it.remove();
1070                changed= true;
1071            }
1072
1073            // add remaining new diffs
1074
while (newIt.hasNext()) {
1075                Object JavaDoc next= newIt.next();
1076                fAdded.add(next);
1077                it.add(next);
1078                changed= true;
1079            }
1080
1081            // shift the old remaining diffs
1082
boolean init= true;
1083            int leftShift= 0;
1084            int rightShift= 0;
1085            while (it.hasNext()) {
1086                current= (RangeDifference) it.next();
1087                if (init) {
1088                    init= false;
1089                    leftShift= last.leftEnd() - current.leftStart();
1090                    rightShift= last.rightEnd() - current.rightStart();
1091                    if (leftShift != 0 || rightShift != 0)
1092                        changed= true;
1093                    else
1094                        break;
1095                }
1096// fChanged.add(current); // not needed since positional shifting is not handled by an annotation model
1097
current.shiftLeft(leftShift);
1098                current.shiftRight(rightShift);
1099            }
1100            
1101            fUpdateNeeded= changed;
1102        }
1103
1104        fLastDifference= null;
1105    }
1106
1107    /**
1108     * Finds a consistent range of at least size before <code>line</code> in the left document.
1109     *
1110     * @param line the line before which the range has to occur
1111     * @param size the minimal size of the range
1112     * @return the first range found, or the first range in the differ if none can be found
1113     */

1114    private RangeDifference findConsistentRangeBeforeLeft(int line, int size) {
1115        RangeDifference found= null;
1116
1117        for (ListIterator JavaDoc it= fDifferences.listIterator(); it.hasNext();) {
1118            RangeDifference difference= (RangeDifference) it.next();
1119            if (found == null || difference.kind() == RangeDifference.NOCHANGE
1120                    && (difference.leftEnd() < line && difference.leftLength() >= size
1121                            || difference.leftEnd() >= line && line - difference.leftStart() >= size))
1122                found= difference;
1123
1124            if (difference.leftEnd() >= line)
1125                break;
1126        }
1127
1128        return found;
1129    }
1130
1131    /**
1132     * Finds a consistent range of at least size after <code>line</code> in the left document.
1133     *
1134     * @param line the line after which the range has to occur
1135     * @param size the minimal size of the range
1136     * @return the first range found, or the last range in the differ if none can be found
1137     */

1138    private RangeDifference findConsistentRangeAfterLeft(int line, int size) {
1139        RangeDifference found= null;
1140
1141        for (ListIterator JavaDoc it= fDifferences.listIterator(fDifferences.size()); it.hasPrevious();) {
1142            RangeDifference difference= (RangeDifference) it.previous();
1143            if (found == null || difference.kind() == RangeDifference.NOCHANGE
1144                    && (difference.leftStart() > line && difference.leftLength() >= size
1145                            || difference.leftStart() <= line && difference.leftEnd() - line >= size))
1146                found= difference;
1147
1148            if (difference.leftStart() <= line)
1149                break;
1150
1151        }
1152
1153        return found;
1154    }
1155
1156    /**
1157     * Finds a consistent range of at least size before <code>line</code> in the right document.
1158     *
1159     * @param line the line before which the range has to occur
1160     * @param size the minimal size of the range
1161     * @return the first range found, or the first range in the differ if none can be found
1162     */

1163    private RangeDifference findConsistentRangeBeforeRight(int line, int size) {
1164        RangeDifference found= null;
1165
1166        int unchanged= -1; // the number of unchanged lines before line
1167
for (ListIterator JavaDoc it= fDifferences.listIterator(); it.hasNext();) {
1168            RangeDifference difference= (RangeDifference) it.next();
1169            if (found == null)
1170                found= difference;
1171            else if (difference.kind() == RangeDifference.NOCHANGE) {
1172                unchanged= Math.min(line, difference.rightEnd()) - difference.rightStart();
1173                if (unchanged >= size)
1174                    found= difference;
1175            }
1176
1177            if (difference.rightEnd() >= line)
1178                break;
1179        }
1180
1181        return found;
1182    }
1183
1184    /**
1185     * Finds a consistent range of at least size after <code>line</code> in the right document.
1186     *
1187     * @param line the line after which the range has to occur
1188     * @param size the minimal size of the range
1189     * @return the first range found, or the last range in the differ if none can be found
1190     */

1191    private RangeDifference findConsistentRangeAfterRight(int line, int size) {
1192        RangeDifference found= null;
1193
1194        int unchanged= -1; // the number of unchanged lines after line
1195
for (ListIterator JavaDoc it= fDifferences.listIterator(fDifferences.size()); it.hasPrevious();) {
1196            RangeDifference difference= (RangeDifference) it.previous();
1197            if (found == null)
1198                found= difference;
1199            else if (difference.kind() == RangeDifference.NOCHANGE) {
1200                unchanged= difference.rightEnd() - Math.max(line + 1, difference.rightStart()); // + 1 to step over the changed line
1201
if (unchanged >= size)
1202                    found= difference;
1203            }
1204
1205            if (difference.rightStart() <= line)
1206                break;
1207        }
1208
1209        return found;
1210    }
1211
1212    /**
1213     * Returns the size of a repetition field starting a <code>line</code>.
1214     *
1215     * @param size the maximal length of the repeat window
1216     * @param doc the document to search
1217     * @param line the line to start searching
1218     * @return the size of a found repetition field, or zero
1219     * @throws BadLocationException if <code>doc</code> is modified concurrently
1220     */

1221    private int searchForRepetitionField(int size, IDocument doc, int line) throws BadLocationException {
1222        /*
1223         Repetition fields: a line wise repetition of maximal size <code>size</code>
1224         can urge a change to come at its end, as diffing greedily takes the longest
1225         unchanged range possible:
1226         <pre>
1227         before
1228         repeat
1229         repeat
1230         repeat
1231         repeat
1232         repeat
1233         repeat
1234         repeat
1235         repeat
1236         after
1237         </pre>
1238
1239         Inserting another repeat element anywhere in the repetition field will create
1240         an addition at its end.
1241
1242         Size is one less than our window size (as this is already one more than the actual number
1243         of affected lines.
1244         */

1245
1246        /*
1247         * Implementation:
1248         * Window of maximum repetition size. Whenever the current matches the first in the window,
1249         * we advance it by one. If there are more free slots in the window, the current line is
1250         * appended.
1251         * We terminate if the current line does not match and there are no more free slots.
1252         *
1253         * Q: what if we have a prefix to a repetition field? Probably does not matter.
1254         */

1255        LinkedList JavaDoc window= new LinkedList JavaDoc();
1256        int nLines= doc.getNumberOfLines();
1257        int repetition= line - 1;
1258        int l= line;
1259
1260        while (l >= 0 && l < nLines) {
1261            IRegion r= doc.getLineInformation(l);
1262            String JavaDoc current= doc.get(r.getOffset(), r.getLength());
1263
1264            if (!window.isEmpty() && window.get(0).equals(current)) {
1265                // repetition found, shift
1266
window.removeFirst();
1267                window.addLast(current);
1268                repetition= l;
1269            } else {
1270                // no repetition, add if there is room
1271
// otherwise return
1272
if (window.size() < size)
1273                    window.addLast(current);
1274                else
1275                    break;
1276            }
1277
1278            l++;
1279        }
1280
1281        int fieldLength= repetition - line + 1;
1282        Assert.isTrue(fieldLength >= 0);
1283        return fieldLength;
1284    }
1285
1286    /**
1287     * Gets the corresponding line on the left side for a line on the right.
1288     *
1289     * @param rightLine the line on the right side
1290     * @return the corresponding left hand line, or <code>-1</code>
1291     */

1292    private int getLeftLine(int rightLine) {
1293        RangeDifference d= getRangeDifferenceForRightLine(rightLine);
1294        if (d == null)
1295            return -1;
1296        return Math.min(d.leftEnd() - 1, d.leftStart() + rightLine - d.rightStart());
1297    }
1298
1299    /**
1300     * Gets the corresponding line on the right side for a line on the left.
1301     *
1302     * @param leftLine the line on the left side
1303     * @return the corresponding right hand line, or <code>-1</code>
1304     */

1305    private int getRightLine(int leftLine) {
1306        RangeDifference d= getRangeDifferenceForLeftLine(leftLine);
1307        if (d == null)
1308            return -1;
1309        return Math.min(d.rightEnd() - 1, d.rightStart() + leftLine - d.leftStart());
1310    }
1311
1312    /**
1313     * Gets the RangeDifference for a line on the left hand side.
1314     *
1315     * @param leftLine the line on the left side
1316     * @return the corresponding RangeDifference, or <code>null</code>
1317     */

1318    private RangeDifference getRangeDifferenceForLeftLine(int leftLine) {
1319        for (Iterator JavaDoc it= fDifferences.iterator(); it.hasNext();) {
1320            RangeDifference d= (RangeDifference) it.next();
1321            if (leftLine >= d.leftStart() && leftLine < d.leftEnd()) {
1322                return d;
1323            }
1324        }
1325        return null;
1326    }
1327
1328    /**
1329     * Gets the RangeDifference for a line on the right hand side.
1330     *
1331     * @param rightLine the line on the right side
1332     * @return the corresponding RangeDifference, or <code>null</code>
1333     */

1334    private RangeDifference getRangeDifferenceForRightLine(int rightLine) {
1335        final List JavaDoc differences= fDifferences;
1336        synchronized (differences) {
1337            for (Iterator JavaDoc it= differences.iterator(); it.hasNext();) {
1338                RangeDifference d= (RangeDifference) it.next();
1339                if (rightLine >= d.rightStart() && rightLine < d.rightEnd()) {
1340                    return d;
1341                }
1342            }
1343        }
1344        return null;
1345    }
1346
1347    /*
1348     * @see org.eclipse.jface.text.source.IAnnotationModel#addAnnotationModelListener(org.eclipse.jface.text.source.IAnnotationModelListener)
1349     */

1350    public void addAnnotationModelListener(IAnnotationModelListener listener) {
1351        fAnnotationModelListeners.add(listener);
1352    }
1353
1354    /*
1355     * @see org.eclipse.jface.text.source.IAnnotationModel#removeAnnotationModelListener(org.eclipse.jface.text.source.IAnnotationModelListener)
1356     */

1357    public void removeAnnotationModelListener(IAnnotationModelListener listener) {
1358        fAnnotationModelListeners.remove(listener);
1359    }
1360
1361    /*
1362     * @see org.eclipse.jface.text.source.IAnnotationModel#connect(org.eclipse.jface.text.IDocument)
1363     */

1364    public void connect(IDocument document) {
1365        Assert.isTrue(fRightDocument == null || fRightDocument == document);
1366
1367        ++fOpenConnections;
1368        if (fOpenConnections == 1) {
1369            fRightDocument= document;
1370            fRightDocument.addDocumentListener(this);
1371            if (document instanceof IDocumentExtension4) {
1372                IDocumentExtension4 ext= (IDocumentExtension4) document;
1373                ext.addDocumentRewriteSessionListener(fSessionListener);
1374            }
1375            initialize();
1376        }
1377    }
1378
1379    /*
1380     * @see org.eclipse.jface.text.source.IAnnotationModel#disconnect(org.eclipse.jface.text.IDocument)
1381     */

1382    public void disconnect(IDocument document) {
1383        Assert.isTrue(fRightDocument == document);
1384
1385        --fOpenConnections;
1386
1387        if (fOpenConnections == 0)
1388            uninstall();
1389    }
1390
1391    /**
1392     * Uninstalls all components and dereferences any objects.
1393     */

1394    private void uninstall() {
1395        Job job= fInitializationJob;
1396        if (job != null)
1397            job.cancel();
1398
1399        synchronized (this) {
1400            fState= SUSPENDED;
1401            fIgnoreDocumentEvents= true;
1402            fInitializationJob= null;
1403
1404            if (fLeftDocument != null)
1405                fLeftDocument.removeDocumentListener(this);
1406            fLeftDocument= null;
1407            fLeftEquivalent= null;
1408
1409            if (fRightDocument != null) {
1410                fRightDocument.removeDocumentListener(this);
1411                if (fRightDocument instanceof IDocumentExtension4) {
1412                    IDocumentExtension4 ext= (IDocumentExtension4) fRightDocument;
1413                    ext.removeDocumentRewriteSessionListener(fSessionListener);
1414                }
1415            }
1416            fRightDocument= null;
1417            fRightEquivalent= null;
1418            
1419            fDifferences.clear();
1420        }
1421
1422        if (fReferenceProvider != null) {
1423            fReferenceProvider.dispose();
1424            fReferenceProvider= null;
1425        }
1426    }
1427
1428    /*
1429     * @see org.eclipse.jface.text.source.IAnnotationModel#addAnnotation(org.eclipse.jface.text.source.Annotation, org.eclipse.jface.text.Position)
1430     */

1431    public void addAnnotation(Annotation annotation, Position position) {
1432        throw new UnsupportedOperationException JavaDoc();
1433    }
1434
1435    /*
1436     * @see org.eclipse.jface.text.source.IAnnotationModel#removeAnnotation(org.eclipse.jface.text.source.Annotation)
1437     */

1438    public void removeAnnotation(Annotation annotation) {
1439        throw new UnsupportedOperationException JavaDoc();
1440    }
1441
1442    /*
1443     * @see org.eclipse.jface.text.source.IAnnotationModel#getAnnotationIterator()
1444     */

1445    public Iterator JavaDoc getAnnotationIterator() {
1446        final List JavaDoc copy;
1447        List JavaDoc differences= fDifferences; // atomic
1448
synchronized (differences) {
1449            copy= new ArrayList JavaDoc(differences);
1450        }
1451        final Iterator JavaDoc iter= copy.iterator();
1452        return new Iterator JavaDoc() {
1453
1454            public void remove() {
1455                throw new UnsupportedOperationException JavaDoc();
1456            }
1457
1458            public boolean hasNext() {
1459                return iter.hasNext();
1460            }
1461
1462            public Object JavaDoc next() {
1463                RangeDifference diff= (RangeDifference) iter.next();
1464                return diff.getDiffRegion(copy, fLeftDocument);
1465            }
1466
1467        };
1468    }
1469
1470    /*
1471     * @see org.eclipse.jface.text.source.IAnnotationModel#getPosition(org.eclipse.jface.text.source.Annotation)
1472     */

1473    public Position getPosition(Annotation annotation) {
1474        if (fRightDocument != null && annotation instanceof DiffRegion) {
1475            RangeDifference difference= ((DiffRegion)annotation).getDifference();
1476            try {
1477                int offset= fRightDocument.getLineOffset(difference.rightStart());
1478                return new Position(offset, fRightDocument.getLineOffset(difference.rightEnd() - 1) + fRightDocument.getLineLength(difference.rightEnd() - 1) - offset);
1479            } catch (BadLocationException e) {
1480                // ignore and return null;
1481
}
1482        }
1483        return null;
1484    }
1485
1486    /**
1487     * Informs all annotation model listeners that this model has been changed.
1488     */

1489    protected void fireModelChanged() {
1490        fireModelChanged(new AnnotationModelEvent(this));
1491    }
1492
1493    /**
1494     * Informs all annotation model listeners that this model has been changed
1495     * as described in the annotation model event. The event is sent out
1496     * to all listeners implementing <code>IAnnotationModelListenerExtension</code>.
1497     * All other listeners are notified by just calling <code>modelChanged(IAnnotationModel)</code>.
1498     *
1499     * @param event the event to be sent out to the listeners
1500     */

1501    protected void fireModelChanged(AnnotationModelEvent event) {
1502        ArrayList JavaDoc v= new ArrayList JavaDoc(fAnnotationModelListeners);
1503        Iterator JavaDoc e= v.iterator();
1504        while (e.hasNext()) {
1505            IAnnotationModelListener l= (IAnnotationModelListener)e.next();
1506            if (l instanceof IAnnotationModelListenerExtension)
1507                 ((IAnnotationModelListenerExtension)l).modelChanged(event);
1508            else
1509                l.modelChanged(this);
1510        }
1511    }
1512
1513    /*
1514     * @see org.eclipse.ui.internal.texteditor.quickdiff.ILineDifferExtension#suspend()
1515     */

1516    public void suspend() {
1517        Job job= fInitializationJob;
1518        if (job != null)
1519            job.cancel();
1520        
1521        synchronized (this) {
1522            fInitializationJob= null;
1523            if (fRightDocument != null)
1524                fRightDocument.removeDocumentListener(this);
1525            if (fLeftDocument != null)
1526                fLeftDocument.removeDocumentListener(this);
1527            fLeftDocument= null;
1528            fLeftEquivalent= null;
1529            
1530            fLastDifference= null;
1531            fStoredEvents.clear();
1532            fDifferences.clear();
1533            
1534            fState= SUSPENDED;
1535            
1536            fireModelChanged();
1537        }
1538    }
1539
1540    /*
1541     * @see org.eclipse.ui.internal.texteditor.quickdiff.ILineDifferExtension#resume()
1542     */

1543    public synchronized void resume() {
1544        if (fRightDocument != null)
1545            fRightDocument.addDocumentListener(this);
1546        initialize();
1547    }
1548}
1549
Popular Tags