| 1 11 package org.eclipse.ui.internal.texteditor.quickdiff; 12 13 import java.util.ArrayList ; 14 import java.util.ConcurrentModificationException ; 15 import java.util.Iterator ; 16 import java.util.LinkedList ; 17 import java.util.List ; 18 import java.util.ListIterator ; 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 80 public class DocumentLineDiffer implements ILineDiffer, IDocumentListener, IAnnotationModel, ILineDifferExtension, ILineDifferExtension2 { 81 82 85 private static class LineChangeInfo implements ILineDiffInfo { 86 87 private static final String [] ORIGINAL_TEXT= new String [] { "\n" }; 89 92 public int getRemovedLinesBelow() { 93 return 0; 94 } 95 96 99 public int getRemovedLinesAbove() { 100 return 0; 101 } 102 103 106 public int getChangeType() { 107 return CHANGED; 108 } 109 110 113 public boolean hasChanges() { 114 return true; 115 } 116 117 120 public String [] getOriginalText() { 121 return ORIGINAL_TEXT; 122 } 123 } 124 125 126 private static boolean DEBUG= "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.ui.workbench.texteditor/debug/DocumentLineDiffer")); 128 129 private static final int INITIALIZE_DELAY= 500; 130 131 132 private static final int SUSPENDED= 0; 133 134 private static final int INITIALIZING= 1; 135 136 private static final int SYNCHRONIZED= 2; 137 138 139 private int fState= SUSPENDED; 140 141 private final ILineDiffInfo fLineChangeInfo= new LineChangeInfo(); 142 143 144 IQuickDiffReferenceProvider fReferenceProvider; 145 146 private int fOpenConnections; 147 148 private IDocument fLeftDocument; 149 153 private DocumentEquivalenceClass fLeftEquivalent; 154 155 private IDocument fRightDocument; 156 160 private DocumentEquivalenceClass fRightEquivalent; 161 165 private boolean fUpdateNeeded; 166 167 private List fAnnotationModelListeners= new ArrayList (); 168 169 private Job fInitializationJob; 170 171 private List fStoredEvents= new ArrayList (); 172 176 private List fDifferences= new ArrayList (); 177 181 private List fRemoved= new ArrayList (); 182 186 private List fAdded= new ArrayList (); 187 191 private List fChanged= new ArrayList (); 192 193 private int fFirstLine; 194 195 private int fNLines; 196 197 private RangeDifference fLastDifference; 198 202 private boolean fIgnoreDocumentEvents= true; 203 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 fThread; 219 private DocumentEvent fLastUIEvent; 220 221 222 225 public DocumentLineDiffer() { 226 } 227 228 229 230 233 public ILineDiffInfo getLineInfo(int line) { 234 235 if (isSuspended()) 236 return fLineChangeInfo; 237 238 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 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 replacement; 267 if (leftLine >= diff.leftEnd()) replacement= ""; 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 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 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 differences= fDifferences; 309 synchronized (differences) { 310 Iterator it= differences.iterator(); 311 312 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 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 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 differences= fDifferences; 363 synchronized (differences) { 364 for (Iterator 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 392 private boolean isInitialized() { 393 return fState == SYNCHRONIZED; 394 } 395 396 401 public synchronized boolean isSynchronized() { 402 return fState == SYNCHRONIZED; 403 } 404 405 410 public synchronized boolean isSuspended() { 411 return fState == SUSPENDED; 412 } 413 414 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 435 public IQuickDiffReferenceProvider getReferenceProvider() { 436 return fReferenceProvider; 437 } 438 439 444 protected synchronized void initialize() { 445 fState= INITIALIZING; 447 448 if (fRightDocument == null) 449 return; 450 451 fIgnoreDocumentEvents= true; 453 454 if (fLeftDocument != null) { 455 fLeftDocument.removeDocumentListener(this); 456 fLeftDocument= null; 457 fLeftEquivalent= null; 458 } 459 460 final Job oldJob= fInitializationJob; 463 if (oldJob != null) { 464 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 479 public IStatus run(IProgressMonitor monitor) { 480 481 if (oldJob != null) 484 try { 485 oldJob.join(); 486 } catch (InterruptedException e) { 487 Assert.isTrue(false); 489 } 490 491 492 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 515 IDocument right= fRightDocument; IDocument actual= null; IDocument reference= null; 519 synchronized (DocumentLineDiffer.this) { 520 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 fLeftDocument= left; 532 fIgnoreDocumentEvents= false; 534 } 535 536 left.addDocumentListener(DocumentLineDiffer.this); 539 540 reference= createCopy(left); 543 if (reference == null) 544 return Status.CANCEL_STATUS; 545 546 548 Object lock= null; 549 if (right instanceof ISynchronizable) 550 lock= ((ISynchronizable) right).getLockObject(); 551 552 if (lock != null) { 553 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 int i= 0; 571 do { 572 if (i++ == 100) 574 return new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK, NLSUtility.format(QuickDiffMessages.quickdiff_error_getting_document_content, new Object [] {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 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 diffs= RangeDifferencer.findRanges(monitor, ref, act); 605 synchronized (DocumentLineDiffer.this) { 608 if (isCanceled(monitor)) 609 return Status.CANCEL_STATUS; 610 611 fDifferences= diffs; 613 } 614 615 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 fInitializationJob= null; 626 fState= SYNCHRONIZED; 627 fLastDifference= null; 628 629 leftEquivalent.setDocument(left); 631 rightEquivalent.setDocument(right); 632 633 break; 634 } 635 636 event= (DocumentEvent) fStoredEvents.remove(0); 637 } 638 639 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 event= new DocumentEvent(copy, event.fOffset, event.fLength, event.fText); 651 handleAboutToBeChanged(event); 652 653 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 696 private IDocument createCopy(IDocument document) { 697 Assert.isNotNull(document); 698 try { 700 return createUnprotectedCopy(document); 701 } catch (NullPointerException e) { 702 } catch (ArrayStoreException e) { 703 } catch (IndexOutOfBoundsException e) { 704 } catch (ConcurrentModificationException e) { 705 } catch (NegativeArraySizeException 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
|