blob: 59b6533f42426c2a4dee605996c9b61456cfb2e9 [file] [log] [blame]
/*
* 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.
*/
/*
* @author max
*/
package com.intellij.extapi.psi;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiInvalidElementAccessException;
import com.intellij.psi.PsiLock;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.impl.source.tree.CompositeElement;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.impl.source.tree.RecursiveTreeElementWalkingVisitor;
import com.intellij.psi.impl.source.tree.SharedImplUtil;
import com.intellij.psi.stubs.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayFactory;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Array;
public class StubBasedPsiElementBase<T extends StubElement> extends ASTDelegatePsiElement {
public static final Key<String> CREATION_TRACE = Key.create("CREATION_TRACE");
private static final Logger LOG = Logger.getInstance("#com.intellij.extapi.psi.StubBasedPsiElementBase");
public static final boolean ourTraceStubAstBinding = "true".equals(System.getProperty("trace.stub.ast.binding", "false"));
private volatile T myStub;
private volatile ASTNode myNode;
private final IElementType myElementType;
public StubBasedPsiElementBase(@NotNull T stub, @NotNull IStubElementType nodeType) {
myStub = stub;
myElementType = nodeType;
myNode = null;
}
public StubBasedPsiElementBase(@NotNull ASTNode node) {
myNode = node;
myElementType = node.getElementType();
}
@Override
@NotNull
public ASTNode getNode() {
ASTNode node = myNode;
if (node == null) {
ApplicationManager.getApplication().assertReadAccessAllowed();
PsiFileImpl file = (PsiFileImpl)getContainingFile();
if (!file.isValid()) throw new PsiInvalidElementAccessException(this);
FileElement treeElement = file.getTreeElement();
StubTree stubTree = file.getStubTree();
if (treeElement != null && myNode == null) {
return notBoundInExistingAst(file, treeElement, stubTree);
}
final FileElement fileElement = file.calcTreeElement();
node = myNode;
if (node == null) {
return failedToBindStubToAst(file, stubTree, fileElement);
}
}
return node;
}
private ASTNode failedToBindStubToAst(PsiFileImpl file, StubTree stubTree, FileElement fileElement) {
VirtualFile vFile = file.getVirtualFile();
String stubString = stubTree != null ? ((PsiFileStubImpl)stubTree.getRoot()).printTree() : "is null";
String astString = DebugUtil.treeToString(fileElement, true);
if (!ourTraceStubAstBinding) {
stubString = StringUtil.trimLog(stubString, 1024);
astString = StringUtil.trimLog(astString, 1024);
}
@NonNls String message = "Failed to bind stub to AST for element " + getClass() + " in " +
(vFile == null ? "<unknown file>" : vFile.getPath()) +
"\nFile:\n" + file.toString() + "@" + System.identityHashCode(file) +
"\nFile stub tree:\n" + stubString +
"\nLoaded file AST:\n" + astString;
if (ourTraceStubAstBinding) {
message += dumpCreationTraces(fileElement);
}
throw new IllegalArgumentException(message);
}
private String dumpCreationTraces(FileElement fileElement) {
final StringBuilder traces = new StringBuilder("\nNow " + Thread.currentThread() + "\n");
traces.append("My creation trace:\n").append(getUserData(CREATION_TRACE));
traces.append("AST creation traces:\n");
fileElement.acceptTree(new RecursiveTreeElementWalkingVisitor(false) {
@Override
public void visitComposite(CompositeElement composite) {
PsiElement psi = composite.getPsi();
if (psi != null) {
traces.append(psi.toString()).append("@").append(System.identityHashCode(psi)).append("\n");
String trace = psi.getUserData(CREATION_TRACE);
if (trace != null) {
traces.append(trace).append("\n");
}
}
super.visitComposite(composite);
}
});
return traces.toString();
}
private ASTNode notBoundInExistingAst(PsiFileImpl file, FileElement treeElement, StubTree stubTree) {
@NonNls String message = "this=" + this.getClass() + "; file.isPhysical=" + file.isPhysical() + "; node=" + myNode + "; file=" + file +
"; tree=" + treeElement + "; stubTree=" + stubTree;
PsiElement each = this;
while (each != null) {
message += "\n each of class " + each.getClass();
if (each instanceof StubBasedPsiElementBase) {
message += "; node=" + ((StubBasedPsiElementBase)each).myNode + "; stub=" + ((StubBasedPsiElementBase)each).myStub;
each = ((StubBasedPsiElementBase)each).getParentByStub();
} else {
break;
}
}
if (ourTraceStubAstBinding) {
message += dumpCreationTraces(treeElement);
}
throw new AssertionError(message);
}
public final void setNode(final ASTNode node) {
myNode = node;
}
@NotNull
@Override
public Language getLanguage() {
return myElementType.getLanguage();
}
@Override
@NotNull
public PsiFile getContainingFile() {
StubElement stub = myStub;
if (stub != null) {
while (!(stub instanceof PsiFileStub)) {
stub = stub.getParentStub();
}
PsiFile psi = (PsiFile)stub.getPsi();
if (psi != null) {
return psi;
}
ApplicationManager.getApplication().assertReadAccessAllowed();
synchronized (PsiLock.LOCK) {
if (myStub != null) {
throw new PsiInvalidElementAccessException(this, "no psi for file stub " + stub + ", invalidation reason=" + ((PsiFileStubImpl<?>) stub).getInvalidationReason(), null);
}
}
}
PsiFile file = super.getContainingFile();
if (file == null) {
throw new PsiInvalidElementAccessException(this);
}
return file;
}
@Override
public boolean isWritable() {
return getContainingFile().isWritable();
}
@Override
public boolean isValid() {
T stub = myStub;
if (stub != null) {
StubElement parent = stub.getParentStub();
if (parent == null) {
LOG.error("No parent for stub " + stub + " of class " + stub.getClass());
return false;
}
PsiElement psi = parent.getPsi();
return psi != null && psi.isValid();
}
return super.isValid();
}
@Override
public PsiManagerEx getManager() {
return (PsiManagerEx)getContainingFile().getManager();
}
@Override
@NotNull
public Project getProject() {
return getContainingFile().getProject();
}
@Override
public boolean isPhysical() {
return getContainingFile().isPhysical();
}
@Override
public PsiElement getContext() {
T stub = myStub;
if (stub != null) {
if (!(stub instanceof PsiFileStub)) {
return stub.getParentStub().getPsi();
}
}
return super.getContext();
}
protected final PsiElement getParentByStub() {
final StubElement<?> stub = getStub();
if (stub != null) {
return stub.getParentStub().getPsi();
}
return SharedImplUtil.getParent(getNode());
}
@Override
public void subtreeChanged() {
super.subtreeChanged();
setStub(null);
}
protected final PsiElement getParentByTree() {
return SharedImplUtil.getParent(getNode());
}
@Override
public PsiElement getParent() {
return getParentByTree();
}
@NotNull
public IStubElementType getElementType() {
if (!(myElementType instanceof IStubElementType)) {
throw new AssertionError("Not a stub type: " + myElementType + " in " + getClass());
}
return (IStubElementType)myElementType;
}
public T getStub() {
ProgressIndicatorProvider.checkCanceled(); // Hope, this is called often
return myStub;
}
public final void setStub(@Nullable T stub) {
myStub = stub;
}
@Nullable
public <Psi extends PsiElement> Psi getStubOrPsiChild(final IStubElementType<? extends StubElement, Psi> elementType) {
T stub = myStub;
if (stub != null) {
final StubElement<Psi> element = stub.findChildStubByType(elementType);
if (element != null) {
return element.getPsi();
}
}
else {
final ASTNode childNode = getNode().findChildByType(elementType);
if (childNode != null) {
return (Psi)childNode.getPsi();
}
}
return null;
}
@NotNull
public <Stub extends StubElement, Psi extends PsiElement> Psi getRequiredStubOrPsiChild(final IStubElementType<Stub, Psi> elementType) {
Psi result = getStubOrPsiChild(elementType);
assert result != null: "Missing required child of type " + elementType + "; tree: "+DebugUtil.psiToString(this, false);
return result;
}
public <Stub extends StubElement, Psi extends PsiElement> Psi[] getStubOrPsiChildren(final IStubElementType<Stub, Psi> elementType, Psi[] array) {
T stub = myStub;
if (stub != null) {
//noinspection unchecked
return (Psi[])stub.getChildrenByType(elementType, array);
}
else {
final ASTNode[] nodes = SharedImplUtil.getChildrenOfType(getNode(), elementType);
Psi[] psiElements = (Psi[])Array.newInstance(array.getClass().getComponentType(), nodes.length);
for (int i = 0; i < nodes.length; i++) {
psiElements[i] = (Psi)nodes[i].getPsi();
}
return psiElements;
}
}
public <Stub extends StubElement, Psi extends PsiElement> Psi[] getStubOrPsiChildren(final IStubElementType<Stub, Psi> elementType, ArrayFactory<Psi> f) {
T stub = myStub;
if (stub != null) {
//noinspection unchecked
return (Psi[])stub.getChildrenByType(elementType, f);
}
else {
final ASTNode[] nodes = SharedImplUtil.getChildrenOfType(getNode(), elementType);
Psi[] psiElements = f.create(nodes.length);
for (int i = 0; i < nodes.length; i++) {
psiElements[i] = (Psi)nodes[i].getPsi();
}
return psiElements;
}
}
public <Psi extends PsiElement> Psi[] getStubOrPsiChildren(TokenSet filter, Psi[] array) {
T stub = myStub;
if (stub != null) {
//noinspection unchecked
return (Psi[])stub.getChildrenByType(filter, array);
}
else {
final ASTNode[] nodes = getNode().getChildren(filter);
Psi[] psiElements = (Psi[])Array.newInstance(array.getClass().getComponentType(), nodes.length);
for (int i = 0; i < nodes.length; i++) {
psiElements[i] = (Psi)nodes[i].getPsi();
}
return psiElements;
}
}
public <Psi extends PsiElement> Psi[] getStubOrPsiChildren(TokenSet filter, ArrayFactory<Psi> f) {
T stub = myStub;
if (stub != null) {
//noinspection unchecked
return (Psi[])stub.getChildrenByType(filter, f);
}
else {
final ASTNode[] nodes = getNode().getChildren(filter);
Psi[] psiElements = f.create(nodes.length);
for (int i = 0; i < nodes.length; i++) {
psiElements[i] = (Psi)nodes[i].getPsi();
}
return psiElements;
}
}
@Nullable
protected <E extends PsiElement> E getStubOrPsiParentOfType(final Class<E> parentClass) {
T stub = myStub;
if (stub != null) {
//noinspection unchecked
return (E)stub.getParentStubOfType(parentClass);
}
return PsiTreeUtil.getParentOfType(this, parentClass);
}
@Nullable
protected PsiElement getStubOrPsiParent() {
T stub = myStub;
if (stub != null) {
//noinspection unchecked
return stub.getParentStub().getPsi();
}
return getParent();
}
@Override
protected Object clone() {
final StubBasedPsiElementBase stubbless = (StubBasedPsiElementBase)super.clone();
stubbless.myStub = null;
return stubbless;
}
}