From 27389fa4984f7de5ea4e6b23d8be70371b5b41dc Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Thu, 15 Oct 2009 16:27:04 +0400 Subject: [PATCH] advanced tree diff which does not replace the whole node if there are many children inserted/removed. it's critical for formatter which should not invalidate composite elements but just replace whitespaces instead --- .../intellij/lang/impl/PsiBuilderImpl.java | 57 +++--- .../tree/events/impl/TreeChangeEventImpl.java | 2 +- .../psi/impl/source/text/ASTDiffBuilder.java | 26 +-- .../source/tree/ASTShallowComparator.java | 10 +- .../psi/impl/source/tree/ASTStructure.java | 9 +- .../source/tree/LazyParseableElement.java | 5 +- .../psi/impl/source/tree/LeafElement.java | 9 - .../injected/InjectedFileViewProvider.java | 2 +- .../impl/CodeInsightTestFixtureImpl.java | 4 +- .../src/com/intellij/util/diff/DiffTree.java | 174 +++++++++--------- .../util/diff/DiffTreeChangeBuilder.java | 8 +- .../diff/FlyweightCapableTreeStructure.java | 7 +- .../com/intellij/util/diff/DiffTreeTest.java | 24 +-- 13 files changed, 149 insertions(+), 188 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java b/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java index b8d110fb0d9c..c132dea8663a 100644 --- a/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java +++ b/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java @@ -30,7 +30,6 @@ import com.intellij.pom.impl.PomTransactionBase; import com.intellij.pom.tree.TreeAspect; import com.intellij.pom.tree.TreeAspectEvent; import com.intellij.pom.tree.events.TreeChangeEvent; -import com.intellij.psi.PsiElement; import com.intellij.psi.PsiErrorElement; import com.intellij.psi.TokenType; import com.intellij.psi.impl.source.PsiFileImpl; @@ -58,11 +57,7 @@ import java.lang.reflect.Field; import java.util.ArrayList; /** - * Created by IntelliJ IDEA. * User: max - * Date: Jan 21, 2005 - * Time: 3:30:29 PM - * To change this template use File | Settings | File Templates. */ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { private static final Logger LOG = Logger.getInstance("#com.intellij.lang.impl.PsiBuilderImpl"); @@ -110,7 +105,6 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { @NonNls private static final String UNBALANCED_MESSAGE = "Unbalanced tree. Most probably caused by unbalanced markers. Try calling setDebugMode(true) against PsiBuilder passed to identify exact location of the problem"; - private PsiElement myInjectionHost; private ITokenTypeRemapper myRemapper; public static void registerWhitespaceToken(IElementType type) { @@ -128,7 +122,6 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { myCharTable = SharedImplUtil.findCharTableByTree(chameleon); myOriginalTree = chameleon.getUserData(BlockSupport.TREE_TO_BE_REPARSED); - myInjectionHost = chameleon.getPsi().getContext(); myFileLevelParsing = myCharTable == null || myOriginalTree != null; cacheLexems(); @@ -158,9 +151,8 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { myLexStarts = new int[approxLexCount]; myLexTypes = new IElementType[approxLexCount]; - int i = 0; - myLexer.start(myText); + int i = 0; while (true) { IElementType type = myLexer.getTokenType(); if (type == null) break; @@ -183,12 +175,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { myComments = tokens; } - @Nullable - public PsiElement getInjectionHost() { - return myInjectionHost; - } - - private static abstract class Node implements LighterASTNode { + private abstract static class Node implements LighterASTNode { public abstract int hc(); } @@ -524,8 +511,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { } private StartMarker createMarker(final int lexemIndex) { - StartMarker marker; - marker = START_MARKERS.alloc(); + StartMarker marker = START_MARKERS.alloc(); marker.myLexemIndex = lexemIndex; marker.myBuilder = this; @@ -701,15 +687,15 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { myConvertor = new ASTConvertor((Node)rootNode); } - public void nodeDeleted(final ASTNode oldParent, final ASTNode oldNode) { + public void nodeDeleted(@NotNull final ASTNode oldParent, @NotNull final ASTNode oldNode) { myDelegate.nodeDeleted(oldParent, oldNode); } - public void nodeInserted(final ASTNode oldParent, final LighterASTNode newNode, final int pos) { + public void nodeInserted(@NotNull final ASTNode oldParent, @NotNull final LighterASTNode newNode, final int pos) { myDelegate.nodeInserted(oldParent, myConvertor.convert((Node)newNode), pos); } - public void nodeReplaced(final ASTNode oldChild, final LighterASTNode newChild) { + public void nodeReplaced(@NotNull final ASTNode oldChild, @NotNull final LighterASTNode newChild) { myDelegate.nodeReplaced(oldChild, myConvertor.convert((Node)newChild)); } @@ -776,11 +762,10 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { StartMarker curNode = rootMarker; - int lastErrorIndex = -1; - Stack nodes = new Stack(); nodes.push(rootMarker); + int lastErrorIndex = -1; for (int i = 1; i < fProduction.size(); i++) { ProductionMarker item = fProduction.get(i); @@ -899,13 +884,20 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { private class MyComparator implements ShallowNodeComparator { public ThreeState deepEqual(final ASTNode oldNode, final LighterASTNode newNode) { + boolean oldIsErrorElement = oldNode instanceof PsiErrorElement; + boolean newIsErrorElement = newNode.getTokenType() == TokenType.ERROR_ELEMENT; + if (oldIsErrorElement != newIsErrorElement) return ThreeState.NO; + if (oldIsErrorElement && newIsErrorElement) { + final PsiErrorElement e1 = (PsiErrorElement)oldNode; + return Comparing.equal(e1.getErrorDescription(), getErrorMessage(newNode)) ? ThreeState.UNSURE : ThreeState.NO; + } + if (newNode instanceof Token) { if (oldNode instanceof ForeignLeafPsiElement) { final IElementType type = newNode.getTokenType(); - if (type instanceof ForeignLeafType) { - return ((ForeignLeafType)type).getValue().equals(oldNode.getText()) ? ThreeState.YES : ThreeState.NO; - } - return ThreeState.NO; + return type instanceof ForeignLeafType && ((ForeignLeafType)type).getValue().equals(oldNode.getText()) + ? ThreeState.YES + : ThreeState.NO; } if (oldNode instanceof LeafElement) { @@ -950,7 +942,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { } if (n1 instanceof PsiErrorElement && n2.getTokenType() == TokenType.ERROR_ELEMENT) { - final PsiErrorElement e1 = ((PsiErrorElement)n1); + final PsiErrorElement e1 = (PsiErrorElement)n1; if (!Comparing.equal(e1.getErrorDescription(), getErrorMessage(n2))) return false; } @@ -975,10 +967,12 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { myRoot = root; } - public LighterASTNode prepareForGetChildren(final LighterASTNode o) { + @NotNull + public LighterASTNode prepareForGetChildren(@NotNull final LighterASTNode o) { return o; } + @NotNull public LighterASTNode getRoot() { return myRoot; } @@ -993,7 +987,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { } private int count; - public int getChildren(final LighterASTNode item, final Ref into) { + public int getChildren(@NotNull final LighterASTNode item, @NotNull final Ref into) { if (item instanceof Token || item instanceof ErrorItem) return 0; StartMarker marker = (StartMarker)item; @@ -1022,7 +1016,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { into.set(old); } else if (count >= old.length) { - LighterASTNode[] newStore = new LighterASTNode[(count * 3) / 2]; + LighterASTNode[] newStore = new LighterASTNode[count * 3 / 2]; System.arraycopy(old, 0, newStore, 0, count); into.set(newStore); } @@ -1145,7 +1139,8 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { private void initCachedField() { try { cachedElementData = (Object[])ourElementDataField.get(this); - } catch(Exception e) { + } + catch (Exception e) { LOG.error(e); } } diff --git a/platform/lang-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java b/platform/lang-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java index abd53c4906c7..def3b1dbc4b8 100644 --- a/platform/lang-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java +++ b/platform/lang-impl/src/com/intellij/pom/tree/events/impl/TreeChangeEventImpl.java @@ -68,11 +68,11 @@ public class TreeChangeEventImpl implements TreeChangeEvent{ } public void addElementaryChange(ASTNode element, ChangeInfo change) { - int depth = 0; final ASTNode parent = element.getTreeParent(); if(parent == null) return; ASTNode currentParent = parent; ASTNode prevParent = element; + int depth = 0; while(currentParent != null){ if(myChangedElements.containsKey(currentParent)){ final TreeChange changesByElement = getChangesByElement(currentParent); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java b/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java index 1936ee9f370a..2b8c0f8a3703 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/text/ASTDiffBuilder.java @@ -33,6 +33,7 @@ import com.intellij.psi.impl.source.DummyHolder; import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.impl.source.tree.*; import com.intellij.util.diff.DiffTreeChangeBuilder; +import org.jetbrains.annotations.NotNull; public class ASTDiffBuilder implements DiffTreeChangeBuilder { private final TreeChangeEventImpl myEvent; @@ -48,7 +49,7 @@ public class ASTDiffBuilder implements DiffTreeChangeBuilder { myEvent = new TreeChangeEventImpl(PomManager.getModel(fileImpl.getProject()).getModelAspect(TreeAspect.class), fileImpl.getTreeElement()); } - public void nodeReplaced(ASTNode oldNode, ASTNode newNode) { + public void nodeReplaced(@NotNull ASTNode oldNode, @NotNull ASTNode newNode) { if (oldNode instanceof FileElement && newNode instanceof FileElement) { BlockSupportImpl.replaceFileElement(myFile, (FileElement)oldNode, (FileElement)newNode, myPsiManager); } @@ -67,7 +68,6 @@ public class ASTDiffBuilder implements DiffTreeChangeBuilder { if (!(newNode instanceof FileElement)) { ((CompositeElement)newNode.getTreeParent()).subtreeChanged(); } - //System.out.println("REPLACED: " + oldNode + " to " + newNode); } } @@ -83,13 +83,12 @@ public class ASTDiffBuilder implements DiffTreeChangeBuilder { } } - public void nodeDeleted(ASTNode parent, final ASTNode child) { + public void nodeDeleted(@NotNull ASTNode parent, @NotNull final ASTNode child) { PsiElement psiParent = parent.getPsi(); PsiElement psiChild = myIsPhysicalScope ? child.getPsi() : null; - PsiTreeChangeEventImpl event = null; if (psiParent != null && psiChild != null) { - event = new PsiTreeChangeEventImpl(myPsiManager); + PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myPsiManager); event.setParent(psiParent); event.setChild(psiChild); myPsiManager.beforeChildRemoval(event); @@ -98,25 +97,14 @@ public class ASTDiffBuilder implements DiffTreeChangeBuilder { myEvent.addElementaryChange(child, ChangeInfoImpl.create(ChangeInfo.REMOVED, child)); ((TreeElement)child).rawRemove(); ((CompositeElement)parent).subtreeChanged(); - - /*if (event != null) { - myPsiManager.childRemoved(event); - }*/ - - //System.out.println("DELETED from " + parent + ": " + child); } - public void nodeInserted(final ASTNode oldParent, ASTNode node, final int pos) { + public void nodeInserted(@NotNull final ASTNode oldParent, @NotNull ASTNode node, final int pos) { transformNewChameleon(oldParent, node); ASTNode anchor = null; for (int i = 0; i < pos; i++) { - if (anchor == null) { - anchor = oldParent.getFirstChildNode(); - } - else { - anchor = anchor.getTreeNext(); - } + anchor = anchor == null ? oldParent.getFirstChildNode() : anchor.getTreeNext(); } ((TreeElement)node).rawRemove(); @@ -135,8 +123,6 @@ public class ASTDiffBuilder implements DiffTreeChangeBuilder { myEvent.addElementaryChange(node, ChangeInfoImpl.create(ChangeInfo.ADD, node)); ((TreeElement)node).clearCaches(); ((CompositeElement)oldParent).subtreeChanged(); - - //System.out.println("INSERTED to " + oldParent + ": " + node + " at " + pos); } public TreeChangeEventImpl getEvent() { diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ASTShallowComparator.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ASTShallowComparator.java index 4b2802c635bf..524fd1b945ce 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ASTShallowComparator.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/ASTShallowComparator.java @@ -40,13 +40,12 @@ public class ASTShallowComparator implements ShallowNodeComparator { private final ASTNode myRoot; - public ASTStructure(final ASTNode root) { + public ASTStructure(@NotNull ASTNode root) { myRoot = root; } - public ASTNode prepareForGetChildren(final ASTNode astNode) { + @NotNull + public ASTNode prepareForGetChildren(@NotNull final ASTNode astNode) { return astNode; } + @NotNull public ASTNode getRoot() { return myRoot; } @@ -41,7 +44,7 @@ public class ASTStructure implements FlyweightCapableTreeStructure { public void disposeChildren(final ASTNode[] nodes, final int count) { } - public int getChildren(final ASTNode astNode, final Ref into) { + public int getChildren(@NotNull final ASTNode astNode, @NotNull final Ref into) { ASTNode child = astNode.getFirstChildNode(); if (child == null) return 0; diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java index df61c01b77d6..4d9ba487495e 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java @@ -30,12 +30,13 @@ public class LazyParseableElement extends CompositeElement { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.LazyParseableElement"); private final Object lock = new String("chameleon parsing lock"); - //private final Object lock = PsiLock.LOCK; private CharSequence myText; public LazyParseableElement(@NotNull IElementType type, CharSequence text) { super(type); - myText = text != null ? text.toString() : null; + synchronized (lock) { + myText = text == null ? null : text.toString(); + } } @NotNull diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/LeafElement.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/LeafElement.java index 82c5dab6bb30..d77a7176faa5 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/LeafElement.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/LeafElement.java @@ -152,15 +152,6 @@ public abstract class LeafElement extends TreeElement { final int len = text.length(); int hc = 0; - if (len > TEXT_MATCHES_THRESHOLD && text instanceof String) { - final String str = (String)text; - - for (int i = 0; i < len; i++) { - hc += str.charAt(i); - } - - return hc; - } for (int i = 0; i < len; i++) { hc += text.charAt(i); } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedFileViewProvider.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedFileViewProvider.java index 03abeabae4ed..c83e5f1e60f1 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedFileViewProvider.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedFileViewProvider.java @@ -57,7 +57,7 @@ class InjectedFileViewProvider extends SingleRootFileViewProvider { public void rootChanged(PsiFile psiFile) { super.rootChanged(psiFile); - if (!isPhysical()) return; // injected PSI change happened + if (!isPhysical()) return; // injected PSI change happened inside reparse; ignore List shreds; synchronized (myLock) { diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java index b83aebb05e4b..8650637d32d5 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java @@ -620,8 +620,6 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig addGutterIconRenderer(info.getGutterIconRenderer(), info.startOffset); } - doHighlighting(); - for (final RangeHighlighter highlighter : myEditor.getDocument().getMarkupModel(project).getAllHighlighters()) { addGutterIconRenderer(highlighter.getGutterIconRenderer(), highlighter.getStartOffset()); } @@ -1063,7 +1061,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig } @NotNull - private List doHighlighting() { + public List doHighlighting() { final Project project = myProjectFixture.getProject(); PsiDocumentManager.getInstance(project).commitAllDocuments(); diff --git a/platform/util/src/com/intellij/util/diff/DiffTree.java b/platform/util/src/com/intellij/util/diff/DiffTree.java index ecc6d5ae39fe..ea63fc6a1ca2 100644 --- a/platform/util/src/com/intellij/util/diff/DiffTree.java +++ b/platform/util/src/com/intellij/util/diff/DiffTree.java @@ -20,7 +20,6 @@ import com.intellij.openapi.util.Ref; import com.intellij.util.ThreeState; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -35,9 +34,8 @@ public class DiffTree { private final DiffTreeChangeBuilder myConsumer; private final List> myOldChildrenLists = new ArrayList>(); private final List> myNewChildrenLists = new ArrayList>(); - private final List myDeepStates = new ArrayList(); - public DiffTree(final FlyweightCapableTreeStructure oldTree, + private DiffTree(final FlyweightCapableTreeStructure oldTree, final FlyweightCapableTreeStructure newTree, final ShallowNodeComparator comparator, final DiffTreeChangeBuilder consumer) { @@ -52,6 +50,13 @@ public class DiffTree { new DiffTree(oldTree, newTree, comparator, consumer).build(oldTree.getRoot(), newTree.getRoot(), 0); } + private static enum CompareResult { + EQUAL, // 100% equal + DRILL_DOWN_NEEDED, // element types are equal, but elements are composite + TYPE_ONLY, // only element types are equal + NOT_EQUAL, // 100% different + } + // TODO: disposeChildren private void build(OT oldN, NT newN, int level) { OT oldNode = myOldTree.prepareForGetChildren(oldN); @@ -63,11 +68,11 @@ public class DiffTree { } final Ref oldChildrenR = myOldChildrenLists.get(level); - final int oldSize = myOldTree.getChildren(oldNode, oldChildrenR); + int oldSize = myOldTree.getChildren(oldNode, oldChildrenR); final OT[] oldChildren = oldChildrenR.get(); final Ref newChildrenR = myNewChildrenLists.get(level); - final int newSize = myNewTree.getChildren(newNode, newChildrenR); + int newSize = myNewTree.getChildren(newNode, newChildrenR); final NT[] newChildren = newChildrenR.get(); if (Math.abs(oldSize - newSize) > CHANGE_PARENT_VERSUS_CHILDREN_THRESHOLD) { @@ -81,120 +86,105 @@ public class DiffTree { if (!comparator.hashcodesEqual(oldNode, newNode) || !comparator.typesEqual(oldNode, newNode)) { myConsumer.nodeReplaced(oldNode, newNode); } - disposeLevel(oldChildren, oldSize, newChildren, newSize); - return; } - boolean walkedDeep = false; + while (oldSize > 0 && newSize > 0) { + OT oldChild1 = oldChildren[oldSize-1]; + NT newChild1 = newChildren[newSize-1]; - ThreeState[] deeps; - if (oldSize == newSize) { - while (myDeepStates.size() <= level) myDeepStates.add(new ThreeState[oldSize]); - deeps = myDeepStates.get(level); - if (deeps.length < oldSize) { - deeps = new ThreeState[oldSize]; - myDeepStates.set(level, deeps); + CompareResult c11 = looksEqual(comparator, oldChild1, newChild1); + + if (c11 != CompareResult.EQUAL && c11 != CompareResult.DRILL_DOWN_NEEDED) { + break; } - else { - Arrays.fill(deeps, 0, oldSize, null); + if (c11 == CompareResult.DRILL_DOWN_NEEDED) { + build(oldChild1, newChild1, level + 1); } - } - else { - deeps = null; + oldSize--; + newSize--; } - int start = 0; - while (start < oldSize && start < newSize) { - OT oldChild = oldChildren[start]; - NT newChild = newChildren[start]; - if (!comparator.typesEqual(oldChild, newChild)) break; - final ThreeState dp = comparator.deepEqual(oldChild, newChild); - if (deeps != null) deeps[start] = dp; + int oldIndex = 0; + int newIndex = 0; + while (oldIndex < oldSize || newIndex < newSize) { + OT oldChild1 = oldIndex < oldSize ? oldChildren[oldIndex] : null; + OT oldChild2 = oldIndex < oldSize-1 ? oldChildren[oldIndex+1] : null; + NT newChild1 = newIndex < newSize ? newChildren[newIndex] : null; + NT newChild2 = newIndex < newSize-1 ? newChildren[newIndex+1] : null; - if (dp != ThreeState.YES) { - if (!comparator.hashcodesEqual(oldChild, newChild)) break; - if (dp == ThreeState.UNSURE) { - build(oldChild, newChild, level + 1); - walkedDeep = true; - } - else if (dp == ThreeState.NO) { - myConsumer.nodeReplaced(oldChild, newChild); + CompareResult c11 = looksEqual(comparator, oldChild1, newChild1); + + if (c11 == CompareResult.EQUAL || c11 == CompareResult.DRILL_DOWN_NEEDED) { + if (c11 == CompareResult.DRILL_DOWN_NEEDED) { + build(oldChild1, newChild1, level+1); } + oldIndex++; + newIndex++; + continue; } - - start++; - } - - int oldEnd = oldSize - 1; - int newEnd = newSize - 1; - - if (oldSize == newSize && start == newSize) { - disposeLevel(oldChildren, oldSize, newChildren, newSize); - return; // No changes at all at this level - } - - while (oldEnd >= start && newEnd >= start) { - OT oldChild = oldChildren[oldEnd]; - NT newChild = newChildren[newEnd]; - if (!comparator.typesEqual(oldChild, newChild)) break; - final ThreeState dp = comparator.deepEqual(oldChild, newChild); - if (deeps != null) deeps[oldEnd] = dp; - if (dp != ThreeState.YES) { - if (!comparator.hashcodesEqual(oldChild, newChild)) break; - if (dp == ThreeState.UNSURE) { - build(oldChild, newChild, level + 1); - walkedDeep = true; + CompareResult c12 = looksEqual(comparator, oldChild1, newChild2); + CompareResult c21 = looksEqual(comparator, oldChild2, newChild1); + if (c11 == CompareResult.TYPE_ONLY) { + if (c21 == CompareResult.EQUAL || c21 == CompareResult.DRILL_DOWN_NEEDED) { + myConsumer.nodeDeleted(oldNode, oldChild1); + oldIndex++; + continue; } - else if (dp == ThreeState.NO) { - myConsumer.nodeReplaced(oldChild, newChild); - } - } - - oldEnd--; - newEnd--; - } - - if (oldSize == newSize) { - for (int i = start; i <= newEnd; i++) { - final OT oldChild = oldChildren[i]; - final NT newChild = newChildren[i]; - - if (comparator.typesEqual(oldChild, newChild)) { - final ThreeState de = deeps[i]; - if (de == ThreeState.UNSURE) { - build(oldChild, newChild, level + 1); - } - else if (de == ThreeState.NO || de == null) { - myConsumer.nodeReplaced(oldChild, newChild); - } + else if (c12 == CompareResult.EQUAL || c12 == CompareResult.DRILL_DOWN_NEEDED) { + myConsumer.nodeInserted(oldNode, newChild1, newIndex); + newIndex++; + continue; } else { - myConsumer.nodeReplaced(oldChild, newChild); + myConsumer.nodeReplaced(oldChild1, newChild1); + oldIndex++; + newIndex++; + continue; } } - } - else { - if (!walkedDeep && start == 0 && newEnd == newSize - 1 && oldEnd == oldSize - 1 && start < oldEnd && start < newEnd) { - myConsumer.nodeReplaced(oldNode, newNode); - disposeLevel(oldChildren, oldSize, newChildren, newSize); - return; + if (c12 == CompareResult.EQUAL || c12 == CompareResult.DRILL_DOWN_NEEDED || c12 == CompareResult.TYPE_ONLY) { + myConsumer.nodeInserted(oldNode, newChild1, newIndex); + newIndex++; + continue; } - for (int i = start; i <= oldEnd; i++) { - final OT oldChild = oldChildren[i]; - myConsumer.nodeDeleted(oldNode, oldChild); + if (c21 == CompareResult.EQUAL || c21 == CompareResult.DRILL_DOWN_NEEDED || c21 == CompareResult.TYPE_ONLY) { + myConsumer.nodeDeleted(oldNode, oldChild1); + oldIndex++; + continue; } - for (int i = start; i <= newEnd; i++) { - myConsumer.nodeInserted(oldNode, newChildren[i], i); + if (oldChild1 == null) { + myConsumer.nodeInserted(oldNode, newChild1, newIndex); + newIndex++; + continue; } + if (newChild1 == null) { + myConsumer.nodeDeleted(oldNode, oldChild1); + oldIndex++; + continue; + } + myConsumer.nodeReplaced(oldChild1, newChild1); + oldIndex++; + newIndex++; } disposeLevel(oldChildren, oldSize, newChildren, newSize); } + private CompareResult looksEqual(ShallowNodeComparator comparator, OT oldChild1, NT newChild1) { + if (oldChild1 == null || newChild1 == null) { + return oldChild1 == newChild1 ? CompareResult.EQUAL : CompareResult.NOT_EQUAL; + } + if (!comparator.typesEqual(oldChild1, newChild1)) return CompareResult.NOT_EQUAL; + ThreeState ret = comparator.deepEqual(oldChild1, newChild1); + if (ret == ThreeState.UNSURE) return CompareResult.DRILL_DOWN_NEEDED; + if (ret == ThreeState.YES) return CompareResult.EQUAL; + return CompareResult.TYPE_ONLY; + } + private void disposeLevel(final OT[] oldChildren, final int oldSize, final NT[] newChildren, final int newSize) { myOldTree.disposeChildren(oldChildren, oldSize); myNewTree.disposeChildren(newChildren, newSize); diff --git a/platform/util/src/com/intellij/util/diff/DiffTreeChangeBuilder.java b/platform/util/src/com/intellij/util/diff/DiffTreeChangeBuilder.java index f541ab3a325c..cf0ac3aae21f 100644 --- a/platform/util/src/com/intellij/util/diff/DiffTreeChangeBuilder.java +++ b/platform/util/src/com/intellij/util/diff/DiffTreeChangeBuilder.java @@ -16,11 +16,13 @@ package com.intellij.util.diff; +import org.jetbrains.annotations.NotNull; + /** * @author max */ public interface DiffTreeChangeBuilder { - void nodeReplaced(OT oldChild, NT newChild); - void nodeDeleted(OT oldParent, OT oldNode); - void nodeInserted(OT oldParent, NT newNode, int pos); + void nodeReplaced(@NotNull OT oldChild, @NotNull NT newChild); + void nodeDeleted(@NotNull OT oldParent, @NotNull OT oldNode); + void nodeInserted(@NotNull OT oldParent, @NotNull NT newNode, int pos); } diff --git a/platform/util/src/com/intellij/util/diff/FlyweightCapableTreeStructure.java b/platform/util/src/com/intellij/util/diff/FlyweightCapableTreeStructure.java index 8676679d2349..1cf1b8f48ff3 100644 --- a/platform/util/src/com/intellij/util/diff/FlyweightCapableTreeStructure.java +++ b/platform/util/src/com/intellij/util/diff/FlyweightCapableTreeStructure.java @@ -17,13 +17,14 @@ package com.intellij.util.diff; import com.intellij.openapi.util.Ref; +import org.jetbrains.annotations.NotNull; /** * @author max */ public interface FlyweightCapableTreeStructure { - T prepareForGetChildren(T node); - T getRoot(); - int getChildren(T parent, Ref into); + @NotNull T prepareForGetChildren(@NotNull T node); + @NotNull T getRoot(); + int getChildren(@NotNull T parent, @NotNull Ref into); void disposeChildren(T[] nodes, int count); } diff --git a/platform/util/testSrc/com/intellij/util/diff/DiffTreeTest.java b/platform/util/testSrc/com/intellij/util/diff/DiffTreeTest.java index b5c9243c3775..008498aebbc1 100644 --- a/platform/util/testSrc/com/intellij/util/diff/DiffTreeTest.java +++ b/platform/util/testSrc/com/intellij/util/diff/DiffTreeTest.java @@ -18,6 +18,7 @@ package com.intellij.util.diff; import com.intellij.openapi.util.Ref; import com.intellij.util.ThreeState; import junit.framework.TestCase; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Arrays; @@ -28,18 +29,10 @@ import java.util.List; */ @SuppressWarnings({"HardCodedStringLiteral"}) public class DiffTreeTest extends TestCase { - private static final Node[] EMPTY = new Node[0]; - private static class Node { private final Node[] myChildren; int myId; - - public Node(final int id) { - myChildren = EMPTY; - myId = id; - } - public Node(final int id, Node... children) { myChildren = children; myId = id; @@ -69,10 +62,12 @@ public class DiffTreeTest extends TestCase { myRoot = root; } - public Node prepareForGetChildren(final Node node) { + @NotNull + public Node prepareForGetChildren(@NotNull final Node node) { return node; } + @NotNull public Node getRoot() { return myRoot; } @@ -80,7 +75,7 @@ public class DiffTreeTest extends TestCase { public void disposeChildren(final Node[] nodes, final int count) { } - public int getChildren(final Node node, final Ref into) { + public int getChildren(@NotNull final Node node, @NotNull final Ref into) { into.set(node.getChildren()); return into.get().length; } @@ -103,15 +98,15 @@ public class DiffTreeTest extends TestCase { public static class DiffBuilder implements DiffTreeChangeBuilder { private final List myResults = new ArrayList(); - public void nodeReplaced(final Node oldNode, final Node newNode) { + public void nodeReplaced(@NotNull final Node oldNode, @NotNull final Node newNode) { myResults.add("REPLACED: " + oldNode + " to " + newNode); } - public void nodeDeleted(final Node parent, final Node child) { + public void nodeDeleted(@NotNull final Node parent, @NotNull final Node child) { myResults.add("DELETED from " + parent + ": " + child); } - public void nodeInserted(final Node oldParent, final Node node, final int pos) { + public void nodeInserted(@NotNull final Node oldParent, @NotNull final Node node, final int pos) { myResults.add("INSERTED to " + oldParent + ": " + node + " at " + pos); } @@ -212,9 +207,8 @@ public class DiffTreeTest extends TestCase { final List expectedList = Arrays.asList(expected); final List actual = result.getEvents(); - if (expectedList.size() > 0 && actual.size() > 0) { + if (!expectedList.isEmpty() && !actual.isEmpty()) { assertEquals(expectedList, actual); } } - }