/*
* Copyright 2000-2016 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.extapi.psi.StubBasedPsiElementBase;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFileWithId;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.impl.source.SubstrateRef;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.stubs.Stub;
import com.intellij.psi.stubs.StubTree;
import com.intellij.reference.SoftReference;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* A lightweight object representing a chain of node indices (among all lazy-parseable and stub-based elements)
* allowing to restore a specific node after it's been garbage-collected and recreated.
*
* @author peter
*/
public abstract class AstPath extends SubstrateRef {
private static final CompositeElement[] REMOVED_PATH_CHILDREN = new CompositeElement[0];
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.AstPath");
private static final Key<CompositeElement[]> PATH_CHILDREN = Key.create("PATH_CHILDREN");
private static final Key<AstPath> NODE_PATH = Key.create("NODE_PATH");
@NotNull
public abstract PsiFileImpl getContainingFile();
@NotNull
public abstract CompositeElement getNode();
@Override
public boolean isValid() {
return getContainingFile().isValid();
}
protected abstract int getDepth();
@Nullable
@Override
public Stub getStub(int stubIndex) {
if (stubIndex < 0) return null;
StubTree stubTree = getFileStubTree();
return stubTree == null ? null : stubTree.getPlainList().get(stubIndex);
}
@Nullable
protected abstract StubTree getFileStubTree();
@Nullable
@Override
public Stub getGreenStub(int stubIndex) {
if (stubIndex < 0) return null;
StubTree stubTree = getContainingFile().getGreenStubTree();
return stubTree == null ? null : stubTree.getPlainList().get(stubIndex);
}
@Nullable
public static AstPath getNodePath(@NotNull CompositeElement node) {
if (node instanceof FileElement) {
PsiElement psi = node.getCachedPsi();
if (!(psi instanceof PsiFileImpl)) return null;
PsiFileImpl file = (PsiFileImpl)psi;
if (!(file.getVirtualFile() instanceof VirtualFileWithId) || file.getElementTypeForStubBuilder() == null) {
return null;
}
return new RootPath(file);
}
return node.getUserData(NODE_PATH);
}
static void cacheNodePaths(@NotNull final LazyParseableElement parent) {
final AstPath parentPath = getNodePath(parent);
if (parentPath == null) {
return;
}
final int depth = parentPath.getDepth() + 1;
final List<CompositeElement> children = ContainerUtil.newArrayList();
parent.acceptTree(new RecursiveTreeElementWalkingVisitor(false) {
@Override
public void visitComposite(CompositeElement composite) {
if (composite != parent && (composite instanceof LazyParseableElement || composite.getElementType() instanceof IStubElementType)) {
int index = children.size();
composite.putUserData(NODE_PATH, depth % 4 == 0 ? new MilestoneChildPath(parentPath, index, depth) : new ChildPath(parentPath, index));
children.add(composite);
}
super.visitComposite(composite);
}
});
parent.putUserData(PATH_CHILDREN, children.toArray(new CompositeElement[0]));
}
public static void invalidatePaths(@NotNull LazyParseableElement scope) {
CompositeElement[] children = scope.getUserData(PATH_CHILDREN);
if (children == null) return;
scope.putUserData(PATH_CHILDREN, REMOVED_PATH_CHILDREN);
for (CompositeElement child : children) {
child.putUserData(NODE_PATH, null);
assertConsistency(child.getCachedPsi());
if (child instanceof LazyParseableElement) {
invalidatePaths((LazyParseableElement)child);
}
}
}
private static void assertConsistency(PsiElement cachedPsi) {
if (cachedPsi instanceof StubBasedPsiElementBase &&
((StubBasedPsiElementBase)cachedPsi).getSubstrateRef() instanceof AstPath) {
LOG.error("Expected strong reference at " + cachedPsi +
" of " + cachedPsi.getClass() +
" and " + ((StubBasedPsiElementBase)cachedPsi).getElementType());
}
}
private static class ChildPath extends AstPath {
private final AstPath myParent;
private final int myIndex;
ChildPath(@NotNull AstPath parent, int index) {
myParent = parent;
myIndex = index;
}
@NotNull
@Override
public PsiFileImpl getContainingFile() {
return myParent.getContainingFile();
}
@NotNull
@Override
public CompositeElement getNode() {
CompositeElement parentNode = myParent.getNode();
//noinspection ResultOfMethodCallIgnored
parentNode.getFirstChildNode(); // expand chameleons, populate PATH_CHILDREN array
CompositeElement[] children = parentNode.getUserData(PATH_CHILDREN);
boolean removed = children == REMOVED_PATH_CHILDREN;
if (children == null || removed) {
throw reportMissingChildren(parentNode, removed);
}
if (myIndex >= children.length) {
throw new AssertionError(myIndex + " >= " + children.length + "; " + parentNode + " of " + parentNode.getClass());
}
return children[myIndex];
}
private AssertionError reportMissingChildren(CompositeElement parentNode, boolean removed) {
String message = "No path children in " + parentNode + " of " + parentNode.getClass() + "; removed=" + removed;
PsiFileImpl file = getContainingFile();
message += "\n file: " + file + " of " + file.getClass() + "; physical=" + file.isPhysical() + "; useStrongRefs=" + file.useStrongRefs();
FileElement fileElement = file.getTreeElement();
message += "\n ast=" + fileElement;
if (fileElement != null) {
CompositeElement[] rootChildren = fileElement.getUserData(PATH_CHILDREN);
message += "; root.children=" + (rootChildren == REMOVED_PATH_CHILDREN ? "removed" : rootChildren == null ? "null" : "nonNull");
}
return new AssertionError(message);
}
@Override
protected int getDepth() {
return 1 + myParent.getDepth();
}
@Override
protected StubTree getFileStubTree() {
return myParent.getFileStubTree();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ChildPath)) return false;
ChildPath path = (ChildPath)o;
return myIndex == path.myIndex && myParent.equals(path.myParent);
}
@Override
public int hashCode() {
return 31 * myParent.hashCode() + myIndex;
}
}
private static class MilestoneChildPath extends ChildPath {
private final int myDepth;
private final PsiFileImpl myFile;
private volatile WeakReference<CompositeElement> myNode;
MilestoneChildPath(@NotNull AstPath parent, int index, int depth) {
super(parent, index);
myDepth = depth;
myFile = parent.getContainingFile();
}
@NotNull
@Override
public CompositeElement getNode() {
CompositeElement node = SoftReference.dereference(myNode);
if (node == null) {
node = super.getNode();
if (myFile.mayCacheAst()) {
myNode = new WeakReference<>(node);
}
}
return node;
}
@Override
protected StubTree getFileStubTree() {
return SoftReference.dereference(myNode) == null ? myFile.getStubTree() : null;
}
@NotNull
@Override
public PsiFileImpl getContainingFile() {
return myFile;
}
@Override
protected int getDepth() {
return myDepth;
}
}
private static class RootPath extends AstPath {
private final PsiFileImpl myFile;
RootPath(@NotNull PsiFileImpl file) {
myFile = file;
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof RootPath && myFile.equals(((RootPath)o).myFile);
}
@Override
public int hashCode() {
return myFile.hashCode();
}
@NotNull
@Override
public PsiFileImpl getContainingFile() {
return myFile;
}
@NotNull
@Override
public CompositeElement getNode() {
return myFile.calcTreeElement();
}
@Override
protected int getDepth() {
return 0;
}
@Override
protected StubTree getFileStubTree() {
return myFile.getStubTree();
}
}
}