use of org.eclipse.compare.rangedifferencer.IRangeComparator in project eclipse.platform.text by eclipse.
the class DocumentLineDiffer method initialize.
/**
* (Re-)initializes the differ using the current reference and <code>DiffInitializer</code>.
*
* @since 3.2 protected for testing reasons, package visible before
*/
protected synchronized void initialize() {
// make new incoming changes go into the queue of stored events, plus signal we can't restore.
fState = INITIALIZING;
if (fRightDocument == null)
return;
// there is no point in receiving updates before the job we get a new copy of the document for diffing
fIgnoreDocumentEvents = true;
if (fLeftDocument != null) {
fLeftDocument.removeDocumentListener(this);
fLeftDocument = null;
fLeftEquivalent = null;
}
// if there already is a job:
// return if it has not started yet, cancel it if already running
final Job oldJob = fInitializationJob;
if (oldJob != null) {
// don't chain up jobs if there is one waiting already.
if (oldJob.getState() == Job.WAITING) {
oldJob.wakeUp(INITIALIZE_DELAY);
return;
}
oldJob.cancel();
}
fInitializationJob = new Job(QuickDiffMessages.quickdiff_initialize) {
/*
* This is run in a different thread. As the documents might be synchronized, never ever
* access the documents in a synchronized section or expect deadlocks. See
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=44692
*/
@Override
public IStatus run(IProgressMonitor monitor) {
// It will return relatively quickly as RangeDifferencer supports canceling
if (oldJob != null)
try {
oldJob.join();
} catch (InterruptedException e) {
// will not happen as no one interrupts our thread
Assert.isTrue(false);
}
// 2: get the reference document
IQuickDiffReferenceProvider provider = fReferenceProvider;
final IDocument left;
try {
left = provider == null ? null : provider.getReference(monitor);
} catch (CoreException e) {
synchronized (DocumentLineDiffer.this) {
if (isCanceled(monitor))
return Status.CANCEL_STATUS;
clearModel();
fireModelChanged();
return e.getStatus();
}
} catch (OperationCanceledException e) {
return Status.CANCEL_STATUS;
}
// Getting our own copies of the documents for offline diffing.
//
// We need to make sure that we do get all document modifications after
// copying the documents as we want to re-inject them later on to become consistent.
// fRightDocument, but not subject to change
IDocument right = fRightDocument;
// the copy of the actual (right) document
IDocument actual = null;
// the copy of the reference (left) document
IDocument reference = null;
synchronized (DocumentLineDiffer.this) {
// 4: take an early exit if the documents are not valid
if (left == null || right == null) {
if (isCanceled(monitor))
return Status.CANCEL_STATUS;
clearModel();
fireModelChanged();
return Status.OK_STATUS;
}
// set the reference document
fLeftDocument = left;
// start listening to document events.
fIgnoreDocumentEvents = false;
}
// accessing the reference document from a different thread - reference providers need
// to be able to deal with this.
left.addDocumentListener(DocumentLineDiffer.this);
// create the reference copy - note that any changes on the
// reference will trigger re-initialization anyway
reference = createCopy(left);
if (reference == null)
return Status.CANCEL_STATUS;
// create the actual copy
Object lock = null;
if (right instanceof ISynchronizable)
lock = ((ISynchronizable) right).getLockObject();
if (lock != null) {
// the document
synchronized (lock) {
synchronized (DocumentLineDiffer.this) {
if (isCanceled(monitor))
return Status.CANCEL_STATUS;
fStoredEvents.clear();
actual = createUnprotectedCopy(right);
}
}
} else {
// b) cannot lock the document
// Now this is fun. The reference documents may be PartiallySynchronizedDocuments
// which will result in a deadlock if they get changed externally before we get
// our exclusive copies.
// Here's what we do: we try over and over (without synchronization) to get copies
// without interleaving modification. If there is a document change, we just repeat.
int i = 0;
do {
// this is an arbitrary emergency exit in case a referenced document goes nuts
if (i++ == 100)
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);
synchronized (DocumentLineDiffer.this) {
if (isCanceled(monitor))
return Status.CANCEL_STATUS;
fStoredEvents.clear();
}
// access documents non synchronized:
// get an exclusive copy of the actual document
actual = createCopy(right);
synchronized (DocumentLineDiffer.this) {
if (isCanceled(monitor))
return Status.CANCEL_STATUS;
if (fStoredEvents.size() == 0 && actual != null)
break;
}
} while (true);
}
IHashFunction hash = new DJBHashFunction();
DocumentEquivalenceClass leftEquivalent = new DocumentEquivalenceClass(reference, hash);
fLeftEquivalent = leftEquivalent;
IRangeComparator ref = new DocEquivalenceComparator(leftEquivalent, null);
DocumentEquivalenceClass rightEquivalent = new DocumentEquivalenceClass(actual, hash);
fRightEquivalent = rightEquivalent;
IRangeComparator act = new DocEquivalenceComparator(rightEquivalent, null);
ArrayList<QuickDiffRangeDifference> diffs = asQuickDiffRangeDifference(RangeDifferencer.findRanges(fRangeDiffFactory, monitor, ref, act));
// re-inject stored events to get up to date.
synchronized (DocumentLineDiffer.this) {
if (isCanceled(monitor))
return Status.CANCEL_STATUS;
// set the new differences so we can operate on them
fDifferences = diffs;
}
// re-inject events accumulated in the meantime.
try {
do {
DocumentEvent event;
synchronized (DocumentLineDiffer.this) {
if (isCanceled(monitor))
return Status.CANCEL_STATUS;
if (fStoredEvents.isEmpty()) {
// we are back in sync with the life documents
fInitializationJob = null;
fState = SYNCHRONIZED;
fLastDifference = null;
// replace the private documents with the actual
leftEquivalent.setDocument(left);
rightEquivalent.setDocument(right);
break;
}
event = fStoredEvents.remove(0);
}
// access documents non synchronized:
IDocument copy = null;
if (event.fDocument == right)
copy = actual;
else if (event.fDocument == left)
copy = reference;
else
Assert.isTrue(false);
// copy the event to inject it into our diff copies
// don't modify the original event! See https://bugs.eclipse.org/bugs/show_bug.cgi?id=134227
event = new DocumentEvent(copy, event.fOffset, event.fLength, event.fText);
handleAboutToBeChanged(event);
// inject the event into our private copy
actual.replace(event.fOffset, event.fLength, event.fText);
handleChanged(event);
} while (true);
} catch (BadLocationException e) {
left.removeDocumentListener(DocumentLineDiffer.this);
clearModel();
initialize();
return Status.CANCEL_STATUS;
}
fireModelChanged();
return Status.OK_STATUS;
}
private boolean isCanceled(IProgressMonitor monitor) {
return fInitializationJob != this || monitor != null && monitor.isCanceled();
}
private void clearModel() {
synchronized (DocumentLineDiffer.this) {
fLeftDocument = null;
fLeftEquivalent = null;
fInitializationJob = null;
fStoredEvents.clear();
fLastDifference = null;
fDifferences.clear();
}
}
/**
* Creates a copy of <code>document</code> and catches any
* exceptions that may occur if the document is modified concurrently.
* Only call this method in a synchronized block if the document is
* an ISynchronizable and has been locked, as document.get() is called
* and may result in a deadlock otherwise.
*
* @param document the document to create a copy of
* @return a copy of the document, or <code>null</code> if an exception was thrown
*/
private IDocument createCopy(IDocument document) {
Assert.isNotNull(document);
// this fixes https://bugs.eclipse.org/bugs/show_bug.cgi?id=56091
try {
return createUnprotectedCopy(document);
} catch (NullPointerException e) {
} catch (ArrayStoreException e) {
} catch (IndexOutOfBoundsException e) {
} catch (ConcurrentModificationException e) {
} catch (NegativeArraySizeException e) {
}
return null;
}
private IDocument createUnprotectedCopy(IDocument document) {
return new Document(document.get());
}
};
fInitializationJob.setSystem(true);
fInitializationJob.setPriority(Job.DECORATE);
fInitializationJob.setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);
fInitializationJob.schedule(INITIALIZE_DELAY);
}
use of org.eclipse.compare.rangedifferencer.IRangeComparator in project eclipse.platform.text by eclipse.
the class DocumentLineDiffer method handleChanged.
/**
* Implementation of documentChanged, non synchronized.
*
* @param event the document event
* @throws BadLocationException if document access fails somewhere
*/
void handleChanged(DocumentEvent event) throws BadLocationException {
Assert.isTrue(fThread == null || fThread == Thread.currentThread());
fThread = null;
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=132125
IDocument left = fLeftDocument;
DocumentEquivalenceClass leftEquivalent = fLeftEquivalent;
DocumentEquivalenceClass rightEquivalent = fRightEquivalent;
if (left == null || leftEquivalent == null || rightEquivalent == null)
return;
// documents: left, right; modified and unchanged are either of both
// TODO two-side
IDocument right = event.getDocument();
IDocument modified = event.getDocument();
if (modified != left && modified != right)
Assert.isTrue(false);
boolean leftToRight = modified == left;
String insertion = event.getText();
int added = insertion == null ? 1 : modified.computeNumberOfLines(insertion) + 1;
// put an upper bound to the delay we can afford
if (added > 50 || fNLines > 50) {
initialize();
return;
}
int size = Math.max(fNLines, added) + 1;
int lineDelta = added - fNLines;
int lastLine = fFirstLine + fNLines - 1;
int repetitionField;
if (leftToRight) {
int originalLine = getRightLine(lastLine + 1);
repetitionField = searchForRepetitionField(size - 1, right, originalLine);
} else {
int originalLine = getLeftLine(lastLine + 1);
repetitionField = searchForRepetitionField(size - 1, left, originalLine);
}
lastLine += repetitionField;
// get enclosing range: search for a consistent block of at least the size of our
// change before and after the change.
final QuickDiffRangeDifference consistentBefore, consistentAfter;
if (leftToRight) {
consistentBefore = findConsistentRangeBeforeLeft(fFirstLine, size);
consistentAfter = findConsistentRangeAfterLeft(lastLine, size);
} else {
consistentBefore = findConsistentRangeBeforeRight(fFirstLine, size);
consistentAfter = findConsistentRangeAfterRight(lastLine, size);
}
// optimize unchanged blocks: if the consistent blocks around the change are larger than
// size, we redimension them (especially important when there are only few changes.
int shiftBefore = 0;
if (consistentBefore.kind() == RangeDifference.NOCHANGE) {
int unchanged;
if (leftToRight)
unchanged = Math.min(fFirstLine, consistentBefore.leftEnd()) - consistentBefore.leftStart();
else
unchanged = Math.min(fFirstLine, consistentBefore.rightEnd()) - consistentBefore.rightStart();
shiftBefore = Math.max(0, unchanged - size);
}
int shiftAfter = 0;
if (consistentAfter.kind() == RangeDifference.NOCHANGE) {
int unchanged;
if (leftToRight)
unchanged = consistentAfter.leftEnd() - Math.max(lastLine + 1, consistentAfter.leftStart());
else
unchanged = consistentAfter.rightEnd() - Math.max(lastLine + 1, consistentAfter.rightStart());
shiftAfter = Math.max(0, unchanged - size);
}
// get the document regions that will be rediffed, take into account that on the
// document, the change has already happened.
// left (reference) document
int leftStartLine = consistentBefore.leftStart() + shiftBefore;
int leftLine = consistentAfter.leftEnd();
if (leftToRight)
leftLine += lineDelta;
int leftEndLine = leftLine - shiftAfter;
ILineRange leftRange = new LineRange(leftStartLine, leftEndLine - leftStartLine);
IRangeComparator reference = new DocEquivalenceComparator(leftEquivalent, leftRange);
// right (actual) document
int rightStartLine = consistentBefore.rightStart() + shiftBefore;
int rightLine = consistentAfter.rightEnd();
if (!leftToRight)
rightLine += lineDelta;
int rightEndLine = rightLine - shiftAfter;
ILineRange rightRange = new LineRange(rightStartLine, rightEndLine - rightStartLine);
IRangeComparator change = new DocEquivalenceComparator(rightEquivalent, rightRange);
// put an upper bound to the delay we can afford
if (leftLine - shiftAfter - leftStartLine > 50 || rightLine - shiftAfter - rightStartLine > 50) {
initialize();
return;
}
// debug
// System.out.println("compare window: "+size+"\n\n<" + left.get(leftRegion.getOffset(), leftRegion.getLength()) + //$NON-NLS-1$//$NON-NLS-2$
// ">\n\n<" + right.get(rightRegion.getOffset(), rightRegion.getLength()) + ">\n"); //$NON-NLS-1$ //$NON-NLS-2$
// compare
List<QuickDiffRangeDifference> diffs = asQuickDiffRangeDifference(RangeDifferencer.findRanges(fRangeDiffFactory, null, reference, change));
if (diffs.size() == 0) {
diffs.add(new QuickDiffRangeDifference(RangeDifference.CHANGE, 0, 0, 0, 0));
}
// shift the partial diffs to the absolute document positions
for (Iterator<QuickDiffRangeDifference> it = diffs.iterator(); it.hasNext(); ) {
QuickDiffRangeDifference d = it.next();
d.shiftLeft(leftStartLine);
d.shiftRight(rightStartLine);
}
// undo optimization shifting
if (shiftBefore > 0) {
QuickDiffRangeDifference first = diffs.get(0);
if (first.kind() == RangeDifference.NOCHANGE)
first.extendStart(-shiftBefore);
else
diffs.add(0, new QuickDiffRangeDifference(RangeDifference.NOCHANGE, first.rightStart() - shiftBefore, shiftBefore, first.leftStart() - shiftBefore, shiftBefore));
}
QuickDiffRangeDifference last = diffs.get(diffs.size() - 1);
if (shiftAfter > 0) {
if (last.kind() == RangeDifference.NOCHANGE)
last.extendEnd(shiftAfter);
else
diffs.add(new QuickDiffRangeDifference(RangeDifference.NOCHANGE, last.rightEnd(), shiftAfter, last.leftEnd(), shiftAfter));
}
// replace changed diff range
synchronized (fDifferences) {
final ListIterator<QuickDiffRangeDifference> it = fDifferences.listIterator();
Iterator<QuickDiffRangeDifference> newIt = diffs.iterator();
QuickDiffRangeDifference current;
boolean changed = false;
// search for consistentBefore
do {
Assert.isTrue(it.hasNext());
current = it.next();
} while (current != consistentBefore);
Assert.isTrue(current == consistentBefore);
fChanged.clear();
fRemoved.clear();
fAdded.clear();
// replace until consistentAfter
while (current != consistentAfter) {
if (newIt.hasNext()) {
QuickDiffRangeDifference o = newIt.next();
if (!current.equals(o)) {
fRemoved.add(current);
fAdded.add(o);
changed = true;
it.set(o);
}
} else {
fRemoved.add(current);
it.remove();
changed = true;
}
Assert.isTrue(it.hasNext());
current = it.next();
}
// replace consistentAfter
Assert.isTrue(current == consistentAfter);
if (newIt.hasNext()) {
QuickDiffRangeDifference o = newIt.next();
if (!current.equals(o)) {
fRemoved.add(current);
fAdded.add(o);
changed = true;
it.set(o);
}
} else {
fRemoved.add(current);
it.remove();
changed = true;
}
// add remaining new diffs
while (newIt.hasNext()) {
QuickDiffRangeDifference next = newIt.next();
fAdded.add(next);
it.add(next);
changed = true;
}
// shift the old remaining diffs
boolean init = true;
int leftShift = 0;
int rightShift = 0;
while (it.hasNext()) {
current = it.next();
if (init) {
init = false;
leftShift = last.leftEnd() - current.leftStart();
rightShift = last.rightEnd() - current.rightStart();
if (leftShift != 0 || rightShift != 0)
changed = true;
else
break;
}
// fChanged.add(current); // not needed since positional shifting is not handled by an annotation model
current.shiftLeft(leftShift);
current.shiftRight(rightShift);
}
fUpdateNeeded = changed;
}
fLastDifference = null;
}
Aggregations