/* * Copyright 2000-2009 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. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.psi.impl.source.tree; import com.intellij.lang.ASTNode; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiLock; import com.intellij.psi.impl.DebugUtil; import com.intellij.psi.impl.ElementBase; import com.intellij.psi.impl.PsiManagerEx; import com.intellij.psi.tree.IElementType; import com.intellij.util.CharTable; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public abstract class TreeElement extends ElementBase implements ASTNode, Cloneable { public static final TreeElement[] EMPTY_ARRAY = new TreeElement[0]; private TreeElement myNextSibling = null; private TreeElement myPrevSibling = null; private CompositeElement myParent = null; private final IElementType myType; private volatile int myStartOffsetInParent = -1; public TreeElement(IElementType type) { myType = type; } @Override public Object clone() { TreeElement clone = (TreeElement)super.clone(); synchronized (PsiLock.LOCK) { clone.myNextSibling = null; clone.myPrevSibling = null; clone.myParent = null; clone.myStartOffsetInParent = -1; } return clone; } @Override public ASTNode copyElement() { CharTable table = SharedImplUtil.findCharTableByTree(this); return ChangeUtil.copyElement(this, table); } public PsiManagerEx getManager() { TreeElement element; for (element = this; element.getTreeParent() != null; element = element.getTreeParent()) { } if (element instanceof FileElement) { //TODO!! return element.getManager(); } else { if (getTreeParent() != null) { return getTreeParent().getManager(); } return null; } } @Override public abstract LeafElement findLeafElementAt(int offset); @NotNull public abstract char[] textToCharArray(); @Override public abstract TreeElement getFirstChildNode(); @Override public abstract TreeElement getLastChildNode(); public abstract int getNotCachedLength(); public abstract int getCachedLength(); @Override public TextRange getTextRange() { int start = getStartOffset(); return new TextRange(start, start + getTextLength()); } @Override public int getStartOffset() { int result = 0; TreeElement current = this; while(current.myParent != null) { result += current.getStartOffsetInParent(); current = current.myParent; } return result; } public final int getStartOffsetInParent() { if (myParent == null) return -1; int offsetInParent = myStartOffsetInParent; if (offsetInParent != -1) return offsetInParent; ApplicationManager.getApplication().assertReadAccessAllowed(); TreeElement cur = this; while (true) { TreeElement prev = cur.getTreePrev(); if (prev == null) break; cur = prev; offsetInParent = cur.myStartOffsetInParent; if (offsetInParent != -1) break; } if (offsetInParent == -1) { cur.myStartOffsetInParent = offsetInParent = 0; } while (cur != this) { TreeElement next = cur.getTreeNext(); offsetInParent += cur.getTextLength(); next.myStartOffsetInParent = offsetInParent; cur = next; } return offsetInParent; } public int getTextOffset() { return getStartOffset(); } public boolean textMatches(@NotNull CharSequence buffer, int startOffset, int endOffset) { return textMatches(buffer, startOffset) == endOffset; } protected abstract int textMatches(@NotNull CharSequence buffer, int start); public boolean textMatches(@NotNull CharSequence seq) { return textMatches(seq, 0, seq.length()); } public boolean textMatches(@NotNull PsiElement element) { return getTextLength() == element.getTextLength() && textMatches(element.getText()); } @NonNls public String toString() { return "Element" + "(" + getElementType().toString() + ")"; } @Override public final CompositeElement getTreeParent() { return myParent; } @Override public final TreeElement getTreePrev() { return myPrevSibling; } final void setTreeParent(CompositeElement parent) { myParent = parent; } final void setTreePrev(TreeElement prev) { myPrevSibling = prev; clearRelativeOffsets(this); } @Override public final TreeElement getTreeNext() { return myNextSibling; } final void setTreeNext(TreeElement next) { myNextSibling = next; clearRelativeOffsets(next); } protected static void clearRelativeOffsets(TreeElement element) { TreeElement cur = element; while (cur != null && cur.myStartOffsetInParent != -1) { cur.myStartOffsetInParent = -1; cur = cur.getTreeNext(); } } public void clearCaches() { } @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"}) public final boolean equals(Object obj) { return obj == this; } public abstract int hc(); // Used in tree diffing public abstract void acceptTree(TreeElementVisitor visitor); protected void onInvalidated() { DebugUtil.onInvalidated(this); } public void rawInsertBeforeMe(@NotNull TreeElement firstNew) { final TreeElement anchorPrev = getTreePrev(); if(anchorPrev == null){ firstNew.rawRemoveUpToLast(); final CompositeElement p = getTreeParent(); if(p != null) p.setFirstChildNode(firstNew); while(true){ final TreeElement treeNext = firstNew.getTreeNext(); assert treeNext != this : "Attempt to create cycle"; firstNew.setTreeParent(p); if(treeNext == null) break; firstNew = treeNext; } setTreePrev(firstNew); firstNew.setTreeNext(this); if (p != null) { p.subtreeChanged(); } } else anchorPrev.rawInsertAfterMe(firstNew); DebugUtil.checkTreeStructure(this); } public void rawInsertAfterMe(@NotNull TreeElement firstNew) { rawInsertAfterMeWithoutNotifications(firstNew); final CompositeElement parent = getTreeParent(); if (parent != null) { parent.subtreeChanged(); } } protected final void rawInsertAfterMeWithoutNotifications(TreeElement firstNew) { firstNew.rawRemoveUpToWithoutNotifications(null, false); final CompositeElement p = getTreeParent(); final TreeElement treeNext = getTreeNext(); firstNew.setTreePrev(this); setTreeNext(firstNew); while(true){ final TreeElement n = firstNew.getTreeNext(); assert n != this : "Attempt to create cycle"; firstNew.setTreeParent(p); if(n == null) break; firstNew = n; } if(treeNext == null){ if(p != null){ firstNew.setTreeParent(p); p.setLastChildNode(firstNew); } } else{ firstNew.setTreeNext(treeNext); treeNext.setTreePrev(firstNew); } DebugUtil.checkTreeStructure(this); } public void rawRemove() { final TreeElement next = getTreeNext(); final CompositeElement parent = getTreeParent(); final TreeElement prev = getTreePrev(); if(prev != null){ prev.setTreeNext(next); } else if(parent != null) { parent.setFirstChildNode(next); } if(next != null){ next.setTreePrev(prev); } else if(parent != null) { parent.setLastChildNode(prev); } DebugUtil.checkTreeStructure(parent); DebugUtil.checkTreeStructure(prev); DebugUtil.checkTreeStructure(next); invalidate(); } public void rawReplaceWithList(TreeElement firstNew) { if (firstNew != null){ rawInsertAfterMeWithoutNotifications(firstNew); } rawRemove(); } protected void invalidate() { CompositeElement parent = getTreeParent(); if (parent != null) { parent.subtreeChanged(); } // invalidate replaced element setTreeNext(null); setTreePrev(null); setTreeParent(null); onInvalidated(); } public void rawRemoveUpToLast() { rawRemoveUpTo(null); } // remove nodes from this[including] to end[excluding] from the parent public void rawRemoveUpTo(@Nullable TreeElement end) { CompositeElement parent = getTreeParent(); rawRemoveUpToWithoutNotifications(end, true); if (parent != null) { parent.subtreeChanged(); } } // remove nodes from this[including] to end[excluding] from the parent protected final void rawRemoveUpToWithoutNotifications(TreeElement end, boolean invalidate) { if(this == end) return; final CompositeElement parent = getTreeParent(); final TreeElement startPrev = getTreePrev(); final TreeElement endPrev = end != null ? end.getTreePrev() : null; assert end == null || end.getTreeParent() == parent : "Trying to remove non-child"; if (end != null) { TreeElement element; for (element = this; element != end && element != null; element = element.getTreeNext()); assert element == end : end + " is not successor of " + this +" in the .getTreeNext() chain"; } if (parent != null){ if (this == parent.getFirstChildNode()) { parent.setFirstChildNode(end); } if (end == null) { parent.setLastChildNode(startPrev); } } if (startPrev != null){ startPrev.setTreeNext(end); } if (end != null){ end.setTreePrev(startPrev); } setTreePrev(null); if (endPrev != null){ endPrev.setTreeNext(null); } if (parent != null){ for(TreeElement element = this; element != null; element = element.getTreeNext()){ element.setTreeParent(null); if (invalidate) { element.onInvalidated(); } } } DebugUtil.checkTreeStructure(parent); DebugUtil.checkTreeStructure(this); } @Override public IElementType getElementType() { return myType; } }