From 16d2f8e45826e1862b4bd76e62cf857b4fce6561 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Fri, 19 Dec 2014 14:12:26 +0300 Subject: [PATCH] persistent range markers are stored in a separate tree since their update is wildly different --- .../openapi/editor/impl/DocumentImpl.java | 24 +++- .../openapi/editor/impl/IntervalTree.java | 7 +- .../openapi/editor/impl/IntervalTreeImpl.java | 118 +++++++++++++----- .../openapi/editor/impl/MarkupModelImpl.java | 52 +------- .../editor/impl/EditorMarkupModelImpl.java | 5 +- 5 files changed, 115 insertions(+), 91 deletions(-) diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java index 0fec2015778f..a9ac8c880521 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java @@ -58,6 +58,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { private final Ref myCachedDocumentListeners = Ref.create(null); private final List myDocumentListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final RangeMarkerTree myRangeMarkers = new RangeMarkerTree(this); + private final RangeMarkerTree myPersistentRangeMarkers = new RangeMarkerTree(this); private final List myGuardedBlocks = new ArrayList(); private ReadonlyFragmentModificationHandler myReadonlyFragmentModificationHandler; @@ -344,9 +345,12 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { return !myIsReadOnly; } + private RangeMarkerTree treeFor(@NotNull RangeMarkerEx rangeMarker) { + return rangeMarker instanceof PersistentRangeMarker ? myPersistentRangeMarkers : myRangeMarkers; + } @Override public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) { - return myRangeMarkers.removeInterval(rangeMarker); + return treeFor(rangeMarker).removeInterval(rangeMarker); } @Override @@ -356,17 +360,17 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { boolean greedyToLeft, boolean greedyToRight, int layer) { - myRangeMarkers.addInterval(rangeMarker, start, end, greedyToLeft, greedyToRight, layer); + treeFor(rangeMarker).addInterval(rangeMarker, start, end, greedyToLeft, greedyToRight, layer); } @TestOnly public int getRangeMarkersSize() { - return myRangeMarkers.size(); + return myRangeMarkers.size() + myPersistentRangeMarkers.size(); } @TestOnly public int getRangeMarkersNodeSize() { - return myRangeMarkers.nodeSize(); + return myRangeMarkers.nodeSize()+myPersistentRangeMarkers.size(); } @Override @@ -1046,12 +1050,20 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { @Override public boolean processRangeMarkers(@NotNull Processor processor) { - return myRangeMarkers.process(processor); + return processRangeMarkersOverlappingWith(0, getTextLength(), processor); } @Override public boolean processRangeMarkersOverlappingWith(int start, int end, @NotNull Processor processor) { - return myRangeMarkers.processOverlappingWith(start, end, processor); + TextRangeInterval interval = new TextRangeInterval(start, end); + IntervalTreeImpl.PeekableIterator iterator = IntervalTreeImpl + .mergingOverlappingIterator(myRangeMarkers, interval, myPersistentRangeMarkers, interval, RangeMarker.BY_START_OFFSET); + try { + return ContainerUtil.process(iterator, processor); + } + finally { + iterator.dispose(); + } } @NotNull diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java index fcb5e9c6ba99..5180838ad849 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,7 @@ package com.intellij.openapi.editor.impl; import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; -/** - * User: cdr - */ -public interface IntervalTree { +interface IntervalTree { boolean process(@NotNull Processor processor); boolean processOverlappingWith(int start, int end, @NotNull Processor processor); boolean processContaining(int offset, @NotNull Processor processor); diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java index 671bcc389089..b07f0b373c29 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java @@ -17,8 +17,6 @@ package com.intellij.openapi.editor.impl; import com.intellij.openapi.editor.ex.DisposableIterator; import com.intellij.openapi.util.Getter; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.TextRange; import com.intellij.util.IncorrectOperationException; import com.intellij.util.Processor; import com.intellij.util.SmartList; @@ -30,9 +28,11 @@ import org.jetbrains.annotations.NotNull; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.List; import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -40,7 +40,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; /** * User: cdr */ -public abstract class IntervalTreeImpl extends RedBlackTree implements IntervalTree { +abstract class IntervalTreeImpl extends RedBlackTree implements IntervalTree { private int keySize; // number of all intervals, counting all duplicates, some of them maybe gced protected final ReadWriteLock l = new ReentrantReadWriteLock(); @@ -48,12 +48,12 @@ public abstract class IntervalTreeImpl extends RedBla private final ReferenceQueue myReferenceQueue = new ReferenceQueue(); private int deadReferenceCount; - protected static class IntervalNode extends RedBlackTree.Node implements MutableInterval { + static class IntervalNode extends RedBlackTree.Node implements MutableInterval { private volatile int myStart; private volatile int myEnd; private static final int ATTACHED_TO_TREE_FLAG = COLOR_FLAG+1; // true if the node is inserted to the tree protected final List> intervals; - protected int maxEnd; // max of all intervalEnd()s among all children. + int maxEnd; // max of all intervalEnd()s among all children. protected int delta; // delta of startOffset. getStartOffset() = myStartOffset + Sum of deltas up to root private volatile long cachedDeltaUpToRoot; // field (packed to long for atomicity) containing deltaUpToRoot, node modCount and allDeltasUpAreNull flag @@ -148,7 +148,7 @@ public abstract class IntervalTreeImpl extends RedBla setFlag(ATTACHED_TO_TREE_FLAG, attached); } - public void removeIntervalInternal(int i) { + void removeIntervalInternal(int i) { intervals.remove(i); if (isAttachedToTree()) { // for detached node, do not update tree node count assert myIntervalTree.keySize > 0 : myIntervalTree.keySize; @@ -156,7 +156,7 @@ public abstract class IntervalTreeImpl extends RedBla } } - public void addInterval(@NotNull E interval) { + void addInterval(@NotNull E interval) { myIntervalTree.assertUnderWriteLock(); intervals.add(createGetter(interval)); if (isAttachedToTree()) { // for detached node, do not update tree node count @@ -170,7 +170,7 @@ public abstract class IntervalTreeImpl extends RedBla } private static class WeakReferencedGetter extends WeakReference implements Getter { - public WeakReferencedGetter(T referent, ReferenceQueue q) { + private WeakReferencedGetter(T referent, ReferenceQueue q) { super(referent, q); } @@ -181,7 +181,7 @@ public abstract class IntervalTreeImpl extends RedBla } } - protected int computeDeltaUpToRoot() { + int computeDeltaUpToRoot() { restart: while (true) { // have to restart on failure to update cached offsets in case of concurrent modification if (!isValid()) return 0; @@ -244,14 +244,14 @@ public abstract class IntervalTreeImpl extends RedBla } } - protected int changeDelta(int change) { + int changeDelta(int change) { if (change != 0) { setCachedValues(0, false, 0); // deltaUpToRoot is not valid anymore return delta += change; } return delta; } - protected void clearDelta() { + void clearDelta() { if (delta != 0) { setCachedValues(0, false, 0); // deltaUpToRoot is not valid anymore delta = 0; @@ -268,7 +268,7 @@ public abstract class IntervalTreeImpl extends RedBla return myEnd = end; } - protected static final int VALID_FLAG = ATTACHED_TO_TREE_FLAG + 1; + static final int VALID_FLAG = ATTACHED_TO_TREE_FLAG + 1; @Override public boolean isValid() { return isFlagSet(VALID_FLAG); @@ -476,7 +476,7 @@ public abstract class IntervalTreeImpl extends RedBla return processOverlappingWith(root.getRight(), start, end, processor, modCountBefore, delta); } - public boolean processOverlappingWithOutside(int start, int end, @NotNull Processor processor) { + boolean processOverlappingWithOutside(int start, int end, @NotNull Processor processor) { try { l.readLock().lock(); checkMax(true); @@ -558,7 +558,7 @@ public abstract class IntervalTreeImpl extends RedBla return processContaining(root.getRight(), offset, processor, modCountBefore, delta); } - public interface PeekableIterator extends DisposableIterator { + interface PeekableIterator extends DisposableIterator { T peek(); PeekableIterator EMPTY = new PeekableIterator() { @Override @@ -589,8 +589,7 @@ public abstract class IntervalTreeImpl extends RedBla } @NotNull - PeekableIterator overlappingIterator(@NotNull final TextRangeInterval rangeInterval) { - + private PeekableIterator overlappingIterator(@NotNull final TextRangeInterval rangeInterval) { l.readLock().lock(); try { @@ -599,6 +598,7 @@ public abstract class IntervalTreeImpl extends RedBla final IntervalNode firstOverlap = findMinOverlappingWith(getRoot(), rangeInterval, modCount, 0); if (firstOverlap == null) { l.readLock().unlock(); + //noinspection unchecked return PeekableIterator.EMPTY; } final int firstOverlapDelta = firstOverlap.computeDeltaUpToRoot(); @@ -725,7 +725,7 @@ public abstract class IntervalTreeImpl extends RedBla return rangeInterval.intersects(start, end); } - protected IntervalNode findOrInsert(@NotNull IntervalNode node) { + IntervalNode findOrInsert(@NotNull IntervalNode node) { assertUnderWriteLock(); node.setRed(); node.setParent(null); @@ -814,15 +814,15 @@ public abstract class IntervalTreeImpl extends RedBla } // returns true if all markers are valid - public boolean checkMax(boolean assertInvalid) { + boolean checkMax(boolean assertInvalid) { return VERIFY && doCheckMax(assertInvalid); } - protected boolean doCheckMax(boolean assertInvalid) { + private boolean doCheckMax(boolean assertInvalid) { try { l.readLock().lock(); - Ref allValid = new Ref(true); + AtomicBoolean allValid = new AtomicBoolean(true); int[] keyCounter = new int[1]; int[] nodeCounter = new int[1]; TLongHashSet ids = new TLongHashSet(keySize); @@ -853,12 +853,13 @@ public abstract class IntervalTreeImpl extends RedBla // returns real (minStart, maxStart, maxEnd) private IntTrinity checkMax(IntervalNode root, - int deltaUpToRootExclusive, - boolean assertInvalid, - Ref allValid, - int[] keyCounter, - int[] nodeCounter, - TLongHashSet ids, boolean allDeltasUpAreNull) { + int deltaUpToRootExclusive, + boolean assertInvalid, + AtomicBoolean allValid, + int[] keyCounter, + int[] nodeCounter, + TLongHashSet ids, + boolean allDeltasUpAreNull) { if (root == null) return new IntTrinity(Integer.MAX_VALUE,Integer.MIN_VALUE,Integer.MIN_VALUE); long packedOffsets = root.cachedDeltaUpToRoot; if (IntervalNode.modCount(packedOffsets) == modCount) { @@ -1004,7 +1005,7 @@ public abstract class IntervalTreeImpl extends RedBla } // returns true if all deltas involved are still 0 - protected boolean pushDelta(IntervalNode root) { + boolean pushDelta(IntervalNode root) { if (root == null || !root.isValid()) return true; IntervalNode parent = root.getParent(); assertAllDeltasAreNull(parent); @@ -1113,7 +1114,7 @@ public abstract class IntervalTreeImpl extends RedBla } // max of n.left's maxend, n.right's maxend and its own interval endOffset - protected void correctMax(@NotNull IntervalNode node, int deltaUpToRoot) { + void correctMax(@NotNull IntervalNode node, int deltaUpToRoot) { if (!node.isValid()) return; int realMax = Math.max(Math.max(maxEndOf(node.getLeft(), deltaUpToRoot), maxEndOf(node.getRight(), deltaUpToRoot)), deltaUpToRoot + node.intervalEnd()); @@ -1234,7 +1235,7 @@ public abstract class IntervalTreeImpl extends RedBla return findMinOverlappingWith(root.getRight(), interval, modCountBefore, delta); } - public void changeData(T interval, int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { + void changeData(T interval, int start, int end, boolean greedyToLeft, boolean greedyToRight, int layer) { try { l.writeLock().lock(); @@ -1360,4 +1361,63 @@ public abstract class IntervalTreeImpl extends RedBla private int maxHeight(Node root) { return root == null ? 0 : 1 + Math.max(maxHeight(root.left), maxHeight(root.right)); } + + // combines iterators for two trees in one using specified comparator + @NotNull + static PeekableIterator mergingOverlappingIterator(@NotNull IntervalTreeImpl tree1, + @NotNull TextRangeInterval tree1Range, + @NotNull IntervalTreeImpl tree2, + @NotNull TextRangeInterval tree2Range, + @NotNull Comparator comparator) { + PeekableIterator exact = tree1.overlappingIterator(tree1Range); + PeekableIterator lines = tree2.overlappingIterator(tree2Range); + return mergeIterators(exact, lines, comparator); + } + + @NotNull + static PeekableIterator mergeIterators(@NotNull final PeekableIterator iterator1, + @NotNull final PeekableIterator iterator2, + @NotNull final Comparator comparator) { + return new PeekableIterator() { + @Override + public void dispose() { + iterator1.dispose(); + iterator2.dispose(); + } + + @Override + public boolean hasNext() { + return iterator1.hasNext() || iterator2.hasNext(); + } + + @Override + public T next() { + return choose().next(); + } + + @NotNull + private PeekableIterator choose() { + T t1 = iterator1.hasNext() ? iterator1.peek() : null; + T t2 = iterator2.hasNext() ? iterator2.peek() : null; + if (t1 == null) { + return iterator2; + } + if (t2 == null) { + return iterator1; + } + int compare = comparator.compare(t1, t2); + return compare < 0 ? iterator1 : iterator2; + } + + @Override + public void remove() { + throw new NoSuchElementException(); + } + + @Override + public T peek() { + return choose().peek(); + } + }; + } } diff --git a/platform/editor-ui-ex/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java b/platform/editor-ui-ex/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java index ecde87920328..20d2f3f6b816 100644 --- a/platform/editor-ui-ex/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java +++ b/platform/editor-ui-ex/src/com/intellij/openapi/editor/impl/MarkupModelImpl.java @@ -48,7 +48,6 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.NoSuchElementException; public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.MarkupModelImpl"); @@ -285,54 +284,9 @@ public class MarkupModelImpl extends UserDataHolderBase implements MarkupModelEx public IntervalTreeImpl.PeekableIterator overlappingIterator(int startOffset, int endOffset) { startOffset = Math.max(0,startOffset); endOffset = Math.max(startOffset, endOffset); - IntervalTreeImpl.PeekableIterator exact = myHighlighterTree.overlappingIterator(new TextRangeInterval(startOffset, endOffset)); - IntervalTreeImpl.PeekableIterator lines = myHighlighterTreeForLines.overlappingIterator(roundToLineBoundaries(startOffset, endOffset)); - return merge(exact, lines); - } - - @NotNull - protected static IntervalTreeImpl.PeekableIterator merge(@NotNull final IntervalTreeImpl.PeekableIterator iterator1, @NotNull final IntervalTreeImpl.PeekableIterator iterator2) { - return new IntervalTreeImpl.PeekableIterator() { - @Override - public void dispose() { - iterator1.dispose(); - iterator2.dispose(); - } - - @Override - public boolean hasNext() { - return iterator1.hasNext() || iterator2.hasNext(); - } - - @Override - public T next() { - return choose().next(); - } - - @NotNull - private IntervalTreeImpl.PeekableIterator choose() { - T t1 = iterator1.hasNext() ? iterator1.peek() : null; - T t2 = iterator2.hasNext() ? iterator2.peek() : null; - if (t1 == null) { - return iterator2; - } - if (t2 == null) { - return iterator1; - } - int compare = RangeHighlighterEx.BY_AFFECTED_START_OFFSET.compare(t1, t2); - return compare < 0 ? iterator1 : iterator2; - } - - @Override - public void remove() { - throw new NoSuchElementException(); - } - - @Override - public T peek() { - return choose().peek(); - } - }; + return IntervalTreeImpl + .mergingOverlappingIterator(myHighlighterTree, new TextRangeInterval(startOffset, endOffset), myHighlighterTreeForLines, + roundToLineBoundaries(startOffset, endOffset), RangeHighlighterEx.BY_AFFECTED_START_OFFSET); } @NotNull diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java index a60fade60d91..814f3a333bb0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java @@ -773,8 +773,9 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark DisposableIterator iterator1 = markup1.overlappingIterator(startOffset, endOffset); DisposableIterator iterator2 = markup2.overlappingIterator(startOffset, endOffset); - IntervalTreeImpl.PeekableIterator iterator = merge((IntervalTreeImpl.PeekableIterator)iterator1, - (IntervalTreeImpl.PeekableIterator)iterator2); + IntervalTreeImpl.PeekableIterator iterator = IntervalTreeImpl + .mergeIterators((IntervalTreeImpl.PeekableIterator)iterator1, + (IntervalTreeImpl.PeekableIterator)iterator2, RangeHighlighterEx.BY_AFFECTED_START_OFFSET); try { ContainerUtil.process(iterator, new Processor() { @Override