blob: f8bfc611ce553ba523cee371009b4aaaf2020714 [file] [log] [blame]
/*
* 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.
* 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.diagnostic.ThreadDumper;
import com.intellij.extapi.psi.ASTDelegatePsiElement;
import com.intellij.lang.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.tree.events.ChangeInfo;
import com.intellij.pom.tree.events.TreeChangeEvent;
import com.intellij.pom.tree.events.impl.ChangeInfoImpl;
import com.intellij.pom.tree.events.impl.ReplaceChangeInfoImpl;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLock;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.impl.FreeThreadedFileViewProvider;
import com.intellij.psi.impl.source.DummyHolder;
import com.intellij.psi.impl.source.DummyHolderElement;
import com.intellij.psi.impl.source.DummyHolderFactory;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.ArrayFactory;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.StringFactory;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class CompositeElement extends TreeElement {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.CompositeElement");
private TreeElement firstChild = null;
private TreeElement lastChild = null;
private volatile int myModificationsCount;
private volatile int myCachedLength = -1;
private volatile int myHC = -1;
private volatile PsiElement myWrapper;
private static final boolean ASSERT_THREADING = true;//DebugUtil.CHECK || ApplicationManagerEx.getApplicationEx().isInternal() || ApplicationManagerEx.getApplicationEx().isUnitTestMode();
public CompositeElement(@NotNull IElementType type) {
super(type);
}
public int getModificationCount() {
return myModificationsCount;
}
@Override
public CompositeElement clone() {
CompositeElement clone = (CompositeElement)super.clone();
synchronized (PsiLock.LOCK) {
clone.firstChild = null;
clone.lastChild = null;
clone.myModificationsCount = 0;
clone.myWrapper = null;
for (ASTNode child = rawFirstChild(); child != null; child = child.getTreeNext()) {
clone.rawAddChildrenWithoutNotifications((TreeElement)child.clone());
}
clone.clearCaches();
}
return clone;
}
public void subtreeChanged() {
synchronized (PsiLock.LOCK) {
CompositeElement compositeElement = this;
while(compositeElement != null) {
compositeElement.clearCaches();
if (!(compositeElement instanceof PsiElement)) {
final PsiElement psi = compositeElement.myWrapper;
if (psi instanceof ASTDelegatePsiElement) {
((ASTDelegatePsiElement)psi).subtreeChanged();
}
else if (psi instanceof PsiFile) {
((PsiFile)psi).subtreeChanged();
}
}
compositeElement = compositeElement.getTreeParent();
}
}
}
@Override
public void clearCaches() {
assertThreading();
myCachedLength = -1;
myModificationsCount++;
myHC = -1;
clearRelativeOffsets(rawFirstChild());
}
public void assertThreading() {
if (ASSERT_THREADING) {
boolean ok = ApplicationManager.getApplication().isWriteAccessAllowed() || isNonPhysicalOrInjected();
if (!ok) {
LOG.error("Threading assertion. " + getThreadingDiagnostics());
}
}
}
private String getThreadingDiagnostics() {
FileElement fileElement;PsiFile psiFile;
return " Under write: " + ApplicationManager.getApplication().isWriteAccessAllowed() +
"; Thread.holdsLock(PsiLock.LOCK): " + Thread.holdsLock(PsiLock.LOCK) +
"; wrapper: " + myWrapper +
"; wrapper.isPhysical(): " + (myWrapper != null && myWrapper.isPhysical()) +
"; fileElement: " + (fileElement = TreeUtil.getFileElement(this)) +
"; psiFile: " + (psiFile = fileElement == null ? null : (PsiFile)fileElement.getPsi()) +
"; psiFile.getViewProvider(): " + (psiFile == null ? null : psiFile.getViewProvider()) +
"; psiFile.isPhysical(): " + (psiFile != null && psiFile.isPhysical()) +
"; nonPhysicalOrInjected: " + isNonPhysicalOrInjected();
}
private boolean isNonPhysicalOrInjected() {
FileElement fileElement = TreeUtil.getFileElement(this);
if (fileElement == null || fileElement instanceof DummyHolderElement) return true;
if (fileElement.getTreeParent() != null) return true; // dummy holder
PsiElement wrapper = this instanceof PsiElement ? (PsiElement)this : myWrapper;
if (wrapper == null) return true;
PsiFile psiFile = wrapper.getContainingFile();
return
psiFile == null ||
psiFile instanceof DummyHolder ||
psiFile.getViewProvider() instanceof FreeThreadedFileViewProvider ||
!psiFile.isPhysical();
}
@Override
public void acceptTree(TreeElementVisitor visitor) {
visitor.visitComposite(this);
}
@Override
public LeafElement findLeafElementAt(int offset) {
TreeElement element = this;
startFind:
while (true) {
TreeElement child = element.getFirstChildNode();
while (child != null) {
final int textLength = child.getTextLength();
if (textLength > offset) {
if (child instanceof LeafElement) {
if (child instanceof ForeignLeafPsiElement) {
child = child.getTreeNext();
continue;
}
return (LeafElement)child;
}
element = child;
continue startFind;
}
offset -= textLength;
child = child.getTreeNext();
}
return null;
}
}
@Nullable
public PsiElement findPsiChildByType(IElementType type) {
final ASTNode node = findChildByType(type);
return node == null ? null : node.getPsi();
}
@Nullable
public PsiElement findPsiChildByType(TokenSet types) {
final ASTNode node = findChildByType(types);
return node == null ? null : node.getPsi();
}
@Override
public ASTNode findChildByType(IElementType type) {
if (DebugUtil.CHECK_INSIDE_ATOMIC_ACTION_ENABLED){
ApplicationManager.getApplication().assertReadAccessAllowed();
}
for(ASTNode element = getFirstChildNode(); element != null; element = element.getTreeNext()){
if (element.getElementType() == type) return element;
}
return null;
}
@Override
public ASTNode findChildByType(IElementType type, ASTNode anchor) {
if (DebugUtil.CHECK_INSIDE_ATOMIC_ACTION_ENABLED){
ApplicationManager.getApplication().assertReadAccessAllowed();
}
ASTNode child = anchor;
while (true) {
if (child == null) return null;
if (type == child.getElementType()) return child;
child = child.getTreeNext();
}
}
@Override
@Nullable
public ASTNode findChildByType(@NotNull TokenSet types) {
if (DebugUtil.CHECK_INSIDE_ATOMIC_ACTION_ENABLED){
ApplicationManager.getApplication().assertReadAccessAllowed();
}
for(ASTNode element = getFirstChildNode(); element != null; element = element.getTreeNext()){
if (types.contains(element.getElementType())) return element;
}
return null;
}
@Override
@Nullable
public ASTNode findChildByType(@NotNull TokenSet typesSet, ASTNode anchor) {
if (DebugUtil.CHECK_INSIDE_ATOMIC_ACTION_ENABLED){
ApplicationManager.getApplication().assertReadAccessAllowed();
}
ASTNode child = anchor;
while (true) {
if (child == null) return null;
if (typesSet.contains(child.getElementType())) return child;
child = child.getTreeNext();
}
}
@Override
@NotNull
public String getText() {
return StringFactory.createShared(textToCharArray());
}
@Override
public CharSequence getChars() {
return getText();
//return new CharArrayCharSequence(textToCharArray());
}
@Override
public int getNotCachedLength() {
final int[] result = {0};
acceptTree(new RecursiveTreeElementWalkingVisitor(false) {
@Override
protected void visitNode(final TreeElement element) {
if (element instanceof LeafElement || TreeUtil.isCollapsedChameleon(element)) {
result[0] += element.getNotCachedLength();
}
super.visitNode(element);
}
});
return result[0];
}
@Override
@NotNull
public char[] textToCharArray() {
ApplicationManager.getApplication().assertReadAccessAllowed();
int startStamp = myModificationsCount;
final int len = getTextLength();
if (startStamp != myModificationsCount) {
throw new AssertionError(
"Tree changed while calculating text. startStamp:"+startStamp+
"; current:"+myModificationsCount+
"; myHC:"+myHC+
"; assertThreading:"+ASSERT_THREADING+
"; this: " + this +
"\n" + getThreadingDiagnostics());
}
char[] buffer = new char[len];
final int endOffset;
try {
endOffset = AstBufferUtil.toBuffer(this, buffer, 0);
}
catch (ArrayIndexOutOfBoundsException e) {
@NonNls String msg = "Underestimated text length: " + len;
msg += diagnoseTextInconsistency(new String(buffer), startStamp);
try {
int length = AstBufferUtil.toBuffer(this, new char[len], 0);
msg += ";\n repetition gives success (" + length + ")";
}
catch (ArrayIndexOutOfBoundsException e1) {
msg += ";\n repetition fails as well";
}
throw new RuntimeException(msg, e);
}
if (endOffset != len) {
@NonNls String msg = "len=" + len + ";\n endOffset=" + endOffset;
msg += diagnoseTextInconsistency(new String(buffer, 0, Math.min(len, endOffset)), startStamp);
throw new AssertionError(msg);
}
return buffer;
}
private String diagnoseTextInconsistency(String text, int startStamp) {
@NonNls String msg = "";
msg += ";\n changed=" + (startStamp != myModificationsCount);
msg += ";\n nonPhysicalOrInjected=" + isNonPhysicalOrInjected();
msg += ";\n buffer=" + text;
try {
msg += ";\n this=" + this;
}
catch (StackOverflowError e) {
msg += ";\n this.toString produces SOE";
}
int shitStart = textMatches(text, 0);
msg += ";\n matches until " + shitStart;
LeafElement leaf = findLeafElementAt(Math.abs(shitStart));
msg += ";\n element there=" + leaf;
if (leaf != null) {
PsiElement psi = leaf.getPsi();
msg += ";\n leaf.text=" + leaf.getText();
msg += ";\n leaf.psi=" + psi;
msg += ";\n leaf.lang=" + (psi == null ? null : psi.getLanguage());
msg += ";\n leaf.type=" + leaf.getElementType();
}
PsiElement psi = getPsi();
if (psi != null) {
boolean valid = psi.isValid();
msg += ";\n psi.valid=" + valid;
if (valid) {
PsiFile file = psi.getContainingFile();
if (file != null) {
msg += ";\n psi.file=" + file;
msg += ";\n psi.file.tl=" + file.getTextLength();
msg += ";\n psi.file.lang=" + file.getLanguage();
msg += ";\n psi.file.vp=" + file.getViewProvider();
msg += ";\n psi.file.vp.lang=" + file.getViewProvider().getLanguages();
msg += ";\n psi.file.vp.lang=" + file.getViewProvider().getLanguages();
PsiElement fileLeaf = file.findElementAt(getTextRange().getStartOffset());
LeafElement myLeaf = findLeafElementAt(0);
msg += ";\n leaves at start=" + fileLeaf + " and " + myLeaf;
}
}
}
return msg;
}
@Override
public boolean textContains(char c) {
for (ASTNode child = getFirstChildNode(); child != null; child = child.getTreeNext()) {
if (child.textContains(c)) return true;
}
return false;
}
@Override
protected int textMatches(@NotNull CharSequence buffer, int start) {
int curOffset = start;
for (TreeElement child = getFirstChildNode(); child != null; child = child.getTreeNext()) {
curOffset = child.textMatches(buffer, curOffset);
if (curOffset < 0) return curOffset;
}
return curOffset;
}
/*
protected int textMatches(final CharSequence buffer, final int start) {
final int[] curOffset = {start};
acceptTree(new RecursiveTreeElementWalkingVisitor() {
@Override
public void visitLeaf(LeafElement leaf) {
matchText(leaf);
}
private void matchText(TreeElement leaf) {
curOffset[0] = leaf.textMatches(buffer, curOffset[0]);
if (curOffset[0] == -1) {
stopWalking();
}
}
@Override
public void visitComposite(CompositeElement composite) {
if (composite instanceof LazyParseableElement && !((LazyParseableElement)composite).isParsed()) {
matchText(composite);
}
else {
super.visitComposite(composite);
}
}
});
return curOffset[0];
}
*/
@Nullable
public final PsiElement findChildByRoleAsPsiElement(int role) {
ASTNode element = findChildByRole(role);
if (element == null) return null;
return SourceTreeToPsiMap.treeElementToPsi(element);
}
@Nullable
public ASTNode findChildByRole(int role) {
// assert ChildRole.isUnique(role);
for (ASTNode child = getFirstChildNode(); child != null; child = child.getTreeNext()) {
if (getChildRole(child) == role) return child;
}
return null;
}
public int getChildRole(ASTNode child) {
LOG.assertTrue(child.getTreeParent() == this, child);
return 0; //ChildRole.NONE;
}
protected final int getChildRole(ASTNode child, int roleCandidate) {
if (findChildByRole(roleCandidate) == child) {
return roleCandidate;
}
return 0; //ChildRole.NONE;
}
@Override
public ASTNode[] getChildren(@Nullable TokenSet filter) {
int count = countChildren(filter);
if (count == 0) {
return EMPTY_ARRAY;
}
final ASTNode[] result = new ASTNode[count];
count = 0;
for (ASTNode child = getFirstChildNode(); child != null; child = child.getTreeNext()) {
if (filter == null || filter.contains(child.getElementType())) {
result[count++] = child;
}
}
return result;
}
@NotNull
public <T extends PsiElement> T[] getChildrenAsPsiElements(@Nullable TokenSet filter, ArrayFactory<T> constructor) {
ApplicationManager.getApplication().assertReadAccessAllowed();
int count = countChildren(filter);
T[] result = constructor.create(count);
if (count == 0) {
return result;
}
int idx = 0;
for (ASTNode child = getFirstChildNode(); child != null && idx < count; child = child.getTreeNext()) {
if (filter == null || filter.contains(child.getElementType())) {
@SuppressWarnings("unchecked") T element = (T)child.getPsi();
LOG.assertTrue(element != null, child);
result[idx++] = element;
}
}
return result;
}
@NotNull
public <T extends PsiElement> T[] getChildrenAsPsiElements(@NotNull IElementType type, ArrayFactory<T> constructor) {
ApplicationManager.getApplication().assertReadAccessAllowed();
int count = countChildren(type);
T[] result = constructor.create(count);
if (count == 0) {
return result;
}
int idx = 0;
for (ASTNode child = getFirstChildNode(); child != null && idx < count; child = child.getTreeNext()) {
if (type == child.getElementType()) {
@SuppressWarnings("unchecked") T element = (T)child.getPsi();
LOG.assertTrue(element != null, child);
result[idx++] = element;
}
}
return result;
}
public int countChildren(@Nullable TokenSet filter) {
// no lock is needed because all chameleons are expanded already
int count = 0;
for (ASTNode child = getFirstChildNode(); child != null; child = child.getTreeNext()) {
if (filter == null || filter.contains(child.getElementType())) {
count++;
}
}
return count;
}
public int countChildren(IElementType type) {
// no lock is needed because all chameleons are expanded already
int count = 0;
for (ASTNode child = getFirstChildNode(); child != null; child = child.getTreeNext()) {
if (type == child.getElementType()) {
count++;
}
}
return count;
}
/**
* @return First element that was appended (for example whitespaces could be skipped)
*/
public TreeElement addInternal(TreeElement first, ASTNode last, ASTNode anchor, Boolean before) {
ASTNode anchorBefore;
if (anchor != null) {
anchorBefore = before.booleanValue() ? anchor : anchor.getTreeNext();
}
else {
anchorBefore = before == null || before.booleanValue() ? null : getFirstChildNode();
}
return (TreeElement)CodeEditUtil.addChildren(this, first, last, anchorBefore);
}
public void deleteChildInternal(@NotNull ASTNode child) {
CodeEditUtil.removeChild(this, child);
}
public void replaceChildInternal(@NotNull ASTNode child, @NotNull TreeElement newElement) {
CodeEditUtil.replaceChild(this, child, newElement);
}
@Override
public int getTextLength() {
int cachedLength = myCachedLength;
if (cachedLength >= 0) return cachedLength;
ApplicationManager.getApplication().assertReadAccessAllowed(); //otherwise a write action can modify the tree while we're walking it
try {
walkCachingLength();
}
catch (AssertionError e) {
myCachedLength = -1;
String assertion = StringUtil.getThrowableText(e);
throw new AssertionError("Walking failure: ===\n"+assertion+"\n=== Thread dump:\n"+ ThreadDumper.dumpThreadsToString()+"\n===\n");
}
return myCachedLength;
}
@Override
public int hc() {
int hc = myHC;
if (hc == -1) {
hc = 0;
TreeElement child = firstChild;
while (child != null) {
hc += child.hc();
child = child.getTreeNext();
}
myHC = hc;
}
return hc;
}
@Override
public int getCachedLength() {
return myCachedLength;
}
@NotNull
private static TreeElement drillDown(@NotNull TreeElement start) {
TreeElement cur = start;
while (cur.getCachedLength() < 0) {
TreeElement child = cur.getFirstChildNode();
if (child == null) {
break;
}
cur = child;
}
return cur;
}
private void walkCachingLength() {
TreeElement cur = drillDown(this);
while (true) {
if (cur.getCachedLength() < 0) {
int length = 0;
for (TreeElement child = cur.getFirstChildNode(); child != null; child = child.getTreeNext()) {
length += child.getTextLength();
}
((CompositeElement)cur).setCachedLength(length);
}
if (cur == this) {
return;
}
TreeElement next = cur.getTreeNext();
cur = next != null ? drillDown(next) : cur.getTreeParent();
}
}
void setCachedLength(int cachedLength) {
myCachedLength = cachedLength;
}
@Override
public TreeElement getFirstChildNode() {
return firstChild;
}
@Override
public TreeElement getLastChildNode() {
return lastChild;
}
void setFirstChildNode(TreeElement firstChild) {
this.firstChild = firstChild;
clearRelativeOffsets(firstChild);
}
void setLastChildNode(TreeElement lastChild) {
this.lastChild = lastChild;
}
@Override
public void addChild(@NotNull ASTNode child, @Nullable final ASTNode anchorBefore) {
LOG.assertTrue(anchorBefore == null || ((TreeElement)anchorBefore).getTreeParent() == this, "anchorBefore == null || anchorBefore.getTreeParent() == parent");
TreeUtil.ensureParsed(getFirstChildNode());
TreeUtil.ensureParsed(child);
final TreeElement last = ((TreeElement)child).getTreeNext();
final TreeElement first = (TreeElement)child;
removeChildrenInner(first, last);
ChangeUtil.prepareAndRunChangeAction(new ChangeUtil.ChangeAction(){
@Override
public void makeChange(TreeChangeEvent destinationTreeChange) {
if (anchorBefore != null) {
insertBefore(destinationTreeChange, (TreeElement)anchorBefore, first);
}
else {
add(destinationTreeChange, CompositeElement.this, first);
}
}
}, this);
}
@Override
public void addLeaf(@NotNull final IElementType leafType, final CharSequence leafText, final ASTNode anchorBefore) {
FileElement holder = new DummyHolder(getManager(), null).getTreeElement();
final LeafElement leaf = ASTFactory.leaf(leafType, holder.getCharTable().intern(leafText));
CodeEditUtil.setNodeGenerated(leaf, true);
holder.rawAddChildren(leaf);
addChild(leaf, anchorBefore);
}
@Override
public void addChild(@NotNull ASTNode child) {
addChild(child, null);
}
@Override
public void removeChild(@NotNull ASTNode child) {
removeChildInner((TreeElement)child);
}
@Override
public void removeRange(@NotNull ASTNode first, ASTNode firstWhichStayInTree) {
removeChildrenInner((TreeElement)first, (TreeElement)firstWhichStayInTree);
}
@Override
public void replaceChild(@NotNull ASTNode oldChild, @NotNull ASTNode newChild) {
LOG.assertTrue(((TreeElement)oldChild).getTreeParent() == this);
final TreeElement oldChild1 = (TreeElement)oldChild;
final TreeElement newChildNext = ((TreeElement)newChild).getTreeNext();
final TreeElement newChild1 = (TreeElement)newChild;
if(oldChild1 == newChild1) return;
removeChildrenInner(newChild1, newChildNext);
ChangeUtil.prepareAndRunChangeAction(new ChangeUtil.ChangeAction(){
@Override
public void makeChange(TreeChangeEvent destinationTreeChange) {
replace(destinationTreeChange, oldChild1, newChild1);
repairRemovedElement(CompositeElement.this, oldChild1);
}
}, this);
}
@Override
public void replaceAllChildrenToChildrenOf(final ASTNode anotherParent) {
TreeUtil.ensureParsed(getFirstChildNode());
TreeUtil.ensureParsed(anotherParent.getFirstChildNode());
final ASTNode firstChild = anotherParent.getFirstChildNode();
ChangeUtil.prepareAndRunChangeAction(new ChangeUtil.ChangeAction(){
@Override
public void makeChange(TreeChangeEvent destinationTreeChange) {
destinationTreeChange.addElementaryChange(anotherParent, ChangeInfoImpl.create(ChangeInfo.CONTENTS_CHANGED, anotherParent));
((CompositeElement)anotherParent).rawRemoveAllChildren();
}
}, (TreeElement)anotherParent);
if (firstChild != null) {
ChangeUtil.prepareAndRunChangeAction(new ChangeUtil.ChangeAction(){
@Override
public void makeChange(TreeChangeEvent destinationTreeChange) {
if(getTreeParent() != null){
final ChangeInfoImpl changeInfo = ChangeInfoImpl.create(ChangeInfo.CONTENTS_CHANGED, CompositeElement.this);
changeInfo.setOldLength(getTextLength());
destinationTreeChange.addElementaryChange(CompositeElement.this, changeInfo);
rawRemoveAllChildren();
rawAddChildren((TreeElement)firstChild);
}
else{
final TreeElement first = getFirstChildNode();
remove(destinationTreeChange, first, null);
add(destinationTreeChange, CompositeElement.this, (TreeElement)firstChild);
repairRemovedElement(CompositeElement.this, first);
}
}
}, this);
}
else {
removeAllChildren();
}
}
public void removeAllChildren() {
final TreeElement child = getFirstChildNode();
if (child != null) {
removeRange(child, null);
}
}
@Override
public void addChildren(ASTNode firstChild, ASTNode lastChild, ASTNode anchorBefore) {
while (firstChild != lastChild) {
final ASTNode next1 = firstChild.getTreeNext();
addChild(firstChild, anchorBefore);
firstChild = next1;
}
}
@Override
public final PsiElement getPsi() {
ProgressIndicatorProvider.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly
PsiElement wrapper = myWrapper;
if (wrapper != null) return wrapper;
synchronized (PsiLock.LOCK) {
wrapper = myWrapper;
if (wrapper != null) return wrapper;
return createAndStorePsi();
}
}
@Override
public <T extends PsiElement> T getPsi(@NotNull Class<T> clazz) {
return LeafElement.getPsi(clazz, getPsi(), LOG);
}
private PsiElement createAndStorePsi() {
PsiElement psi = createPsiNoLock();
myWrapper = psi;
return psi;
}
protected PsiElement createPsiNoLock() {
final Language lang = getElementType().getLanguage();
final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang);
if (parserDefinition != null) {
return parserDefinition.createElement(this);
}
//noinspection ConstantConditions
return null;
}
public void setPsi(@NotNull PsiElement psi) {
myWrapper = psi;
}
protected void clearPsi() {
myWrapper = null;
}
public final void rawAddChildren(@NotNull TreeElement first) {
rawAddChildrenWithoutNotifications(first);
subtreeChanged();
}
public void rawAddChildrenWithoutNotifications(TreeElement first) {
final TreeElement last = getLastChildNode();
if (last == null){
first.rawRemoveUpToWithoutNotifications(null, false);
setFirstChildNode(first);
while(true){
final TreeElement treeNext = first.getTreeNext();
first.setTreeParent(this);
if(treeNext == null) break;
first = treeNext;
}
setLastChildNode(first);
first.setTreeParent(this);
}
else {
last.rawInsertAfterMeWithoutNotifications(first);
}
DebugUtil.checkTreeStructure(this);
}
public void rawRemoveAllChildren() {
TreeElement first = getFirstChildNode();
if (first != null) {
first.rawRemoveUpToLast();
}
}
// creates PSI and stores to the 'myWrapper', if not created already
void createAllChildrenPsiIfNecessary() {
final List<CompositeElement> nodes = ContainerUtil.newArrayList();
final List<PsiElement> psiElements = ContainerUtil.newArrayList();
RecursiveTreeElementWalkingVisitor visitor = new RecursiveTreeElementWalkingVisitor(false) {
@Override
public void visitLeaf(LeafElement leaf) {
}
@Override
public void visitComposite(CompositeElement composite) {
ProgressIndicatorProvider.checkCanceled(); // we can safely interrupt creating children PSI any moment
if (composite.myWrapper == null) {
nodes.add(composite);
psiElements.add(composite.createPsiNoLock());
}
super.visitComposite(composite);
}
};
for(TreeElement child = getFirstChildNode(); child != null; child = child.getTreeNext()) {
child.acceptTree(visitor);
}
synchronized (PsiLock.LOCK) { // guard for race condition with getPsi()
for (int i = 0; i < psiElements.size(); i++) {
CompositeElement node = nodes.get(i);
if (node.myWrapper == null) {
node.myWrapper = psiElements.get(i);
}
}
}
}
private static void repairRemovedElement(final CompositeElement oldParent, final TreeElement oldChild) {
if(oldChild == null) return;
final FileElement treeElement = DummyHolderFactory.createHolder(oldParent.getManager(), null, false).getTreeElement();
treeElement.rawAddChildren(oldChild);
}
private static void add(final TreeChangeEvent destinationTreeChange,
final CompositeElement parent,
final TreeElement first) {
parent.rawAddChildren(first);
TreeElement child = first;
while(child != null){
destinationTreeChange.addElementaryChange(child, ChangeInfoImpl.create(ChangeInfo.ADD, child));
child = child.getTreeNext();
}
}
private static void remove(final TreeChangeEvent destinationTreeChange,
final TreeElement first,
final TreeElement last) {
if (first != null) {
TreeElement child = first;
while(child != last && child != null){
destinationTreeChange.addElementaryChange(child, ChangeInfoImpl.create(ChangeInfo.REMOVED, child));
child = child.getTreeNext();
}
first.rawRemoveUpTo(last);
}
}
private static void insertBefore(final TreeChangeEvent destinationTreeChange,
final TreeElement anchorBefore,
final TreeElement first) {
anchorBefore.rawInsertBeforeMe(first);
TreeElement child = first;
while(child != anchorBefore){
destinationTreeChange.addElementaryChange(child, ChangeInfoImpl.create(ChangeInfo.ADD, child));
child = child.getTreeNext();
}
}
private static void replace(final TreeChangeEvent sourceTreeChange,
final TreeElement oldChild,
final TreeElement newChild) {
oldChild.rawReplaceWithList(newChild);
final ReplaceChangeInfoImpl change = new ReplaceChangeInfoImpl(newChild);
sourceTreeChange.addElementaryChange(newChild, change);
change.setReplaced(oldChild);
}
private static void removeChildInner(final TreeElement child) {
removeChildrenInner(child, child.getTreeNext());
}
private static void removeChildrenInner(final TreeElement first, final TreeElement last) {
final FileElement fileElement = TreeUtil.getFileElement(first);
if (fileElement != null) {
ChangeUtil.prepareAndRunChangeAction(new ChangeUtil.ChangeAction() {
@Override
public void makeChange(TreeChangeEvent destinationTreeChange) {
remove(destinationTreeChange, first, last);
repairRemovedElement(fileElement, first);
}
}, first.getTreeParent());
}
else {
first.rawRemoveUpTo(last);
}
}
public TreeElement rawFirstChild() {
return firstChild;
}
public TreeElement rawLastChild() {
return lastChild;
}
}