Search in sources :

Example 1 with DocEquivalenceComparator

use of org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DocEquivalenceComparator 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);
}
Also used : IHashFunction(org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.IHashFunction) IStatus(org.eclipse.core.runtime.IStatus) ConcurrentModificationException(java.util.ConcurrentModificationException) OperationCanceledException(org.eclipse.core.runtime.OperationCanceledException) ArrayList(java.util.ArrayList) IRangeComparator(org.eclipse.compare.rangedifferencer.IRangeComparator) Document(org.eclipse.jface.text.Document) IDocument(org.eclipse.jface.text.IDocument) Job(org.eclipse.core.runtime.jobs.Job) DJBHashFunction(org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DJBHashFunction) IStatus(org.eclipse.core.runtime.IStatus) Status(org.eclipse.core.runtime.Status) DocumentEquivalenceClass(org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DocumentEquivalenceClass) ISynchronizable(org.eclipse.jface.text.ISynchronizable) DocumentEvent(org.eclipse.jface.text.DocumentEvent) IProgressMonitor(org.eclipse.core.runtime.IProgressMonitor) CoreException(org.eclipse.core.runtime.CoreException) IQuickDiffReferenceProvider(org.eclipse.ui.texteditor.quickdiff.IQuickDiffReferenceProvider) DocEquivalenceComparator(org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DocEquivalenceComparator) IDocument(org.eclipse.jface.text.IDocument) BadLocationException(org.eclipse.jface.text.BadLocationException)

Example 2 with DocEquivalenceComparator

use of org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DocEquivalenceComparator 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;
}
Also used : DocumentEquivalenceClass(org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DocumentEquivalenceClass) IRangeComparator(org.eclipse.compare.rangedifferencer.IRangeComparator) LineRange(org.eclipse.jface.text.source.LineRange) ILineRange(org.eclipse.jface.text.source.ILineRange) ILineRange(org.eclipse.jface.text.source.ILineRange) DocEquivalenceComparator(org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DocEquivalenceComparator) IDocument(org.eclipse.jface.text.IDocument)

Aggregations

IRangeComparator (org.eclipse.compare.rangedifferencer.IRangeComparator)2 IDocument (org.eclipse.jface.text.IDocument)2 DocEquivalenceComparator (org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DocEquivalenceComparator)2 DocumentEquivalenceClass (org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DocumentEquivalenceClass)2 ArrayList (java.util.ArrayList)1 ConcurrentModificationException (java.util.ConcurrentModificationException)1 CoreException (org.eclipse.core.runtime.CoreException)1 IProgressMonitor (org.eclipse.core.runtime.IProgressMonitor)1 IStatus (org.eclipse.core.runtime.IStatus)1 OperationCanceledException (org.eclipse.core.runtime.OperationCanceledException)1 Status (org.eclipse.core.runtime.Status)1 Job (org.eclipse.core.runtime.jobs.Job)1 BadLocationException (org.eclipse.jface.text.BadLocationException)1 Document (org.eclipse.jface.text.Document)1 DocumentEvent (org.eclipse.jface.text.DocumentEvent)1 ISynchronizable (org.eclipse.jface.text.ISynchronizable)1 ILineRange (org.eclipse.jface.text.source.ILineRange)1 LineRange (org.eclipse.jface.text.source.LineRange)1 DJBHashFunction (org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.DJBHashFunction)1 IHashFunction (org.eclipse.ui.internal.texteditor.quickdiff.compare.equivalence.IHashFunction)1