/*
* 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;
import com.intellij.extapi.psi.StubBasedPsiElementBase;
import com.intellij.ide.util.PsiNavigationSupport;
import com.intellij.lang.*;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Queryable;
import com.intellij.openapi.util.Factory;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileWithId;
import com.intellij.psi.*;
import com.intellij.psi.impl.*;
import com.intellij.psi.impl.file.PsiFileImplUtil;
import com.intellij.psi.impl.file.impl.FileManagerImpl;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.psi.impl.source.text.BlockSupportImpl;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.stubs.*;
import com.intellij.psi.tree.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.reference.SoftReference;
import com.intellij.util.FileContentUtilCore;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PatchedWeakReference;
import com.intellij.util.concurrency.AtomicFieldUpdater;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.CharArrayUtil;
import consulo.annotations.RequiredReadAction;
import consulo.annotations.RequiredWriteAction;
import consulo.ide.IconDescriptorUpdaters;
import consulo.lang.LanguageVersion;
import consulo.psi.PsiElementWithSubtreeChangeNotifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.lang.reflect.Array;
import java.util.*;
public abstract class PsiFileImpl extends UserDataHolderBase implements PsiFileEx, PsiElementWithSubtreeChangeNotifier, PsiFileWithStubSupport, Queryable {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.PsiFileImpl");
public static final String STUB_PSI_MISMATCH = "stub-psi mismatch";
private static final AtomicFieldUpdater<PsiFileImpl, FileTrees> ourTreeUpdater =
AtomicFieldUpdater.forFieldOfType(PsiFileImpl.class, FileTrees.class);
private IElementType myElementType;
protected IElementType myContentElementType;
private long myModificationStamp;
protected PsiFile myOriginalFile;
private final FileViewProvider myViewProvider;
private volatile FileTrees myTrees = FileTrees.noStub(null, this);
private boolean myInvalidated;
@SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
private AstPathPsiMap myRefToPsi;
private final ThreadLocal<FileElement> myFileElementBeingLoaded = new ThreadLocal<>();
protected final PsiManagerEx myManager;
public static final Key<Boolean> BUILDING_STUB = new Key<>("Don't use stubs mark!");
protected PsiFileImpl(@NotNull IElementType elementType, IElementType contentElementType, @NotNull FileViewProvider provider) {
this(provider);
init(elementType, contentElementType);
}
protected PsiFileImpl(@NotNull FileViewProvider provider ) {
myManager = (PsiManagerEx)provider.getManager();
myViewProvider = provider;
myRefToPsi = new AstPathPsiMap(getProject());
}
public void setContentElementType(final IElementType contentElementType) {
LOG.assertTrue(contentElementType instanceof ILazyParseableElementType, contentElementType);
myContentElementType = contentElementType;
}
public IElementType getContentElementType() {
return myContentElementType;
}
protected void init(@NotNull final IElementType elementType, final IElementType contentElementType) {
myElementType = elementType;
setContentElementType(contentElementType);
}
public TreeElement createContentLeafElement(CharSequence leafText) {
if (myContentElementType instanceof ILazyParseableElementType) {
return ASTFactory.lazy((ILazyParseableElementType)myContentElementType, leafText);
}
return ASTFactory.leaf(myContentElementType, leafText);
}
@Override
public boolean isDirectory() {
return false;
}
@Nullable
public FileElement getTreeElement() {
FileElement node = derefTreeElement();
if (node != null) return node;
if (!getViewProvider().isPhysical()) {
return loadTreeElement();
}
return null;
}
protected FileElement derefTreeElement() {
return myTrees.derefTreeElement();
}
@Override
public VirtualFile getVirtualFile() {
return getViewProvider().isEventSystemEnabled() ? getViewProvider().getVirtualFile() : null;
}
@Override
public boolean processChildren(final PsiElementProcessor<PsiFileSystemItem> processor) {
return true;
}
@Override
public boolean isValid() {
if (myManager.getProject().isDisposed()) {
// normally FileManager.dispose would call markInvalidated
// but there's temporary disposed project in tests, which doesn't actually dispose its components :(
return false;
}
if (!myViewProvider.getVirtualFile().isValid()) {
// PSI listeners receive VFS deletion events and do markInvalidated
// but some VFS listeners receive the same events before that and ask PsiFile.isValid
return false;
}
return !myInvalidated;
}
@Override
public void markInvalidated() {
myInvalidated = true;
DebugUtil.onInvalidated(this);
}
@Override
public boolean isContentsLoaded() {
return derefTreeElement() != null;
}
@NotNull
private FileElement loadTreeElement() {
ApplicationManager.getApplication().assertReadAccessAllowed();
final FileViewProvider viewProvider = getViewProvider();
if (viewProvider.isPhysical() && myManager.isAssertOnFileLoading(viewProvider.getVirtualFile())) {
LOG.error("Access to tree elements not allowed in tests. path='" + viewProvider.getVirtualFile().getPresentableUrl()+"'");
}
FileElement treeElement = createFileElement(viewProvider.getContents());
treeElement.setPsi(this);
myFileElementBeingLoaded.set(treeElement);
try {
while (true) {
FileTrees trees = myTrees;
List<Pair<StubBasedPsiElementBase, AstPath>> bindings = calcStubAstBindings(treeElement, trees);
FileElement savedTree = ensureTreeElement(viewProvider, treeElement, trees, bindings);
if (savedTree != null) {
return savedTree;
}
}
}
finally {
myFileElementBeingLoaded.remove();
}
}
@Nullable
private FileElement ensureTreeElement(@NotNull FileViewProvider viewProvider,
@NotNull FileElement treeElement,
@NotNull FileTrees trees,
@NotNull List<Pair<StubBasedPsiElementBase, AstPath>> bindings) {
synchronized (PsiLock.LOCK) {
FileElement existing = derefTreeElement();
if (existing != null) {
return existing;
}
if (trees != myTrees) {
return null; // try again
}
switchFromStubToAst(bindings, trees);
updateTrees(trees.withAst(createTreeElementPointer(treeElement)));
if (LOG.isDebugEnabled() && viewProvider.isPhysical()) {
LOG.debug("Loaded text for file " + viewProvider.getVirtualFile().getPresentableUrl());
}
return treeElement;
}
}
@RequiredReadAction
@Override
public ASTNode findTreeForStub(StubTree tree, StubElement<?> stub) {
final Iterator<StubElement<?>> stubs = tree.getPlainList().iterator();
final StubElement<?> root = stubs.next();
final CompositeElement ast = calcTreeElement();
if (root == stub) return ast;
return findTreeForStub(ast, stubs, stub);
}
@Nullable
private static ASTNode findTreeForStub(ASTNode tree, final Iterator<StubElement<?>> stubs, final StubElement stub) {
final IElementType type = tree.getElementType();
if (type instanceof IStubElementType && ((IStubElementType) type).shouldCreateStub(tree)) {
final StubElement curStub = stubs.next();
if (curStub == stub) return tree;
}
for (ASTNode node : tree.getChildren(null)) {
final ASTNode treeForStub = findTreeForStub(node, stubs, stub);
if (treeForStub != null) return treeForStub;
}
return null;
}
private void switchFromStubToAst(List<Pair<StubBasedPsiElementBase, AstPath>> bindings, FileTrees trees) {
if (!bindings.isEmpty() && trees.useStrongRefs) {
List<String> psiStrings = ContainerUtil.map(bindings, pair -> pair.first.getClass().getName());
LOG.error(this + " of " + getClass() + "; " + psiStrings);
}
for (int i = 0; i < bindings.size(); i++) {
Pair<StubBasedPsiElementBase, AstPath> pair = bindings.get(i);
StubBasedPsiElementBase psi = pair.first;
AstPath path = pair.second;
path.getNode().setPsi(psi);
myRefToPsi.cachePsi(path, psi);
psi.setStubIndex(i + 1);
}
}
private List<Pair<StubBasedPsiElementBase, AstPath>> calcStubAstBindings(@NotNull FileElement root, FileTrees trees) {
final StubTree stubTree = trees.derefStub();
if (stubTree == null || trees.astLoaded) { // don't bind green stub to AST: the PSI should already be cached in myRefToPsi
return Collections.emptyList();
}
final Iterator<StubElement<?>> stubs = stubTree.getPlainList().iterator();
stubs.next(); // Skip file stub;
final List<Pair<StubElement, AstPath>> result = ContainerUtil.newArrayList();
final IStubFileElementType elementType = getElementTypeForStubBuilder();
assert elementType != null;
final StubBuilder builder = elementType.getBuilder();
root.acceptTree(new RecursiveTreeElementWalkingVisitor() {
@Override
protected void visitNode(TreeElement node) {
CompositeElement parent = node.getTreeParent();
if (parent != null && builder.skipChildProcessingWhenBuildingStubs(parent, node)) {
return;
}
IElementType type = node.getElementType();
if (type instanceof IStubElementType && ((IStubElementType)type).shouldCreateStub(node)) {
if (!stubs.hasNext()) {
reportStubAstMismatch("Stub list is less than AST, last AST element: " + node.getElementType() + " " + node, stubTree);
}
final StubElement stub = stubs.next();
if (stub.getStubType() != node.getElementType()) {
reportStubAstMismatch("Stub and PSI element type mismatch in " + getName() + ": stub " + stub + ", AST " +
node.getElementType() + "; " + node, stubTree);
}
AstPath path = AstPath.getNodePath((CompositeElement)node);
assert path != null;
result.add(Pair.create(stub, path));
}
super.visitNode(node);
}
});
if (stubs.hasNext()) {
reportStubAstMismatch("Stub list in " + getName() + " has more elements than PSI", stubTree);
}
synchronized (PsiLock.LOCK) {
return ContainerUtil.map(result, pair -> {
StubElement stub = pair.first;
PsiElement psi = stub.getPsi();
assert psi != null : "Stub " + stub + " (" + stub.getClass() + ") has returned null PSI";
return Pair.create((StubBasedPsiElementBase)psi, pair.second);
});
}
}
@Nullable
public IStubFileElementType getElementTypeForStubBuilder() {
ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(getLanguage());
IFileElementType type = definition == null ? null : definition.getFileNodeType();
return type instanceof IStubFileElementType ? (IStubFileElementType)type : null;
}
void reportStubAstMismatch(String message, StubTree stubTree) {
rebuildStub();
updateTrees(myTrees.clearStub(STUB_PSI_MISMATCH));
throw StubTreeLoader.getInstance().stubTreeAndIndexDoNotMatch(message, stubTree, this);
}
@NotNull
protected FileElement createFileElement(CharSequence docText) {
final FileElement treeElement;
final TreeElement contentLeaf = createContentLeafElement(docText);
if (contentLeaf instanceof FileElement) {
treeElement = (FileElement)contentLeaf;
}
else {
final CompositeElement xxx = ASTFactory.composite(myElementType);
assert xxx instanceof FileElement : "BUMM";
treeElement = (FileElement)xxx;
treeElement.rawAddChildrenWithoutNotifications(contentLeaf);
}
return treeElement;
}
public void clearCaches() {
myModificationStamp ++;
}
@RequiredReadAction
@Override
public String getText() {
final ASTNode tree = derefTreeElement();
if (!isValid()) {
// even invalid PSI can calculate its text by concatenating its children
if (tree != null) return tree.getText();
throw new PsiInvalidElementAccessException(this);
}
String string = getViewProvider().getContents().toString();
if (tree != null && string.length() != tree.getTextLength()) {
throw new AssertionError("File text mismatch: tree.length=" + tree.getTextLength() +
"; psi.length=" + string.length() +
"; this=" + this +
"; vp=" + getViewProvider());
}
return string;
}
@RequiredReadAction
@Override
public int getTextLength() {
final ASTNode tree = derefTreeElement();
if (tree != null) return tree.getTextLength();
PsiUtilCore.ensureValid(this);
return getViewProvider().getContents().length();
}
@RequiredReadAction
@Override
public TextRange getTextRange() {
return new TextRange(0, getTextLength());
}
@RequiredReadAction
@Override
public PsiElement getNextSibling() {
return SharedPsiElementImplUtil.getNextSibling(this);
}
@RequiredReadAction
@Override
public PsiElement getPrevSibling() {
return SharedPsiElementImplUtil.getPrevSibling(this);
}
@Override
public long getModificationStamp() {
PsiElement context = getContext();
PsiFile contextFile = context == null || !context.isValid() ? null : context.getContainingFile();
long contextStamp = contextFile == null ? 0 : contextFile.getModificationStamp();
return myModificationStamp + contextStamp;
}
@Override
public void subtreeChanged() {
doClearCaches("subtreeChanged");
getViewProvider().rootChanged(this);
}
private void doClearCaches(String reason) {
final FileElement tree = getTreeElement();
if (tree != null) {
tree.clearCaches();
}
synchronized (PsiLock.LOCK) {
updateTrees(myTrees.clearStub(reason));
}
clearCaches();
}
@Override
@SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException", "CloneDoesntCallSuperClone"})
protected PsiFileImpl clone() {
FileViewProvider viewProvider = getViewProvider();
FileViewProvider providerCopy = viewProvider.clone();
final Language language = getLanguage();
if (providerCopy == null) {
throw new AssertionError("Unable to clone the view provider: " + viewProvider + "; " + language);
}
PsiFileImpl clone = BlockSupportImpl.getFileCopy(this, providerCopy);
copyCopyableDataTo(clone);
clone.myRefToPsi = new AstPathPsiMap(getProject());
if (getTreeElement() != null) {
// not set by provider in clone
final FileElement treeClone = (FileElement)calcTreeElement().clone();
clone.setTreeElementPointer(treeClone); // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963)
treeClone.setPsi(clone);
}
if (viewProvider.isEventSystemEnabled()) {
clone.myOriginalFile = this;
}
else if (myOriginalFile != null) {
clone.myOriginalFile = myOriginalFile;
}
FileManagerImpl.clearPsiCaches(providerCopy);
return clone;
}
@RequiredReadAction
@Override
@NotNull public String getName() {
return getViewProvider().getVirtualFile().getName();
}
@RequiredWriteAction
@Override
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
checkSetName(name);
doClearCaches("setName");
return PsiFileImplUtil.setName(this, name);
}
@Override
public void checkSetName(String name) throws IncorrectOperationException {
if (!getViewProvider().isEventSystemEnabled()) return;
PsiFileImplUtil.checkSetName(this, name);
}
@Override
public boolean isWritable() {
return getViewProvider().getVirtualFile().isWritable();
}
@Override
public PsiDirectory getParent() {
return getContainingDirectory();
}
@Override
@Nullable
public PsiDirectory getContainingDirectory() {
VirtualFile file = getViewProvider().getVirtualFile();
final VirtualFile parentFile = file.getParent();
if (parentFile == null) return null;
if (!parentFile.isValid()) {
LOG.error("Invalid parent: " + parentFile + " of file " + file + ", file.valid=" + file.isValid());
return null;
}
return getManager().findDirectory(parentFile);
}
@Override
@NotNull
public PsiFile getContainingFile() {
return this;
}
@Override
public void delete() throws IncorrectOperationException {
checkDelete();
PsiFileImplUtil.doDelete(this);
}
@Override
public void checkDelete() throws IncorrectOperationException {
if (!getViewProvider().isEventSystemEnabled()) {
throw new IncorrectOperationException();
}
CheckUtil.checkWritable(this);
}
@Override
@NotNull
public PsiFile getOriginalFile() {
return myOriginalFile == null ? this : myOriginalFile;
}
public void setOriginalFile(@NotNull final PsiFile originalFile) {
myOriginalFile = originalFile.getOriginalFile();
}
@Override
@NotNull
public PsiFile[] getPsiRoots() {
final FileViewProvider viewProvider = getViewProvider();
final Set<Language> languages = viewProvider.getLanguages();
final PsiFile[] roots = new PsiFile[languages.size()];
int i = 0;
for (Language language : languages) {
PsiFile psi = viewProvider.getPsi(language);
if (psi == null) {
LOG.error("PSI is null for "+language+"; in file: "+this);
}
roots[i++] = psi;
}
if (roots.length > 1) {
Arrays.sort(roots, FILE_BY_LANGUAGE_ID);
}
return roots;
}
private static final Comparator<PsiFile> FILE_BY_LANGUAGE_ID = Comparator.comparing(o -> o.getLanguage().getID());
@Override
public boolean isPhysical() {
// TODO[ik] remove this shit with dummy file system
return getViewProvider().isEventSystemEnabled();
}
@RequiredReadAction
@Override
@NotNull
public Language getLanguage() {
return myElementType.getLanguage();
}
@NotNull
@Override
public LanguageVersion getLanguageVersion() {
return PsiTreeUtil.getLanguageVersion(this);
}
@Override
@NotNull
public FileViewProvider getViewProvider() {
return myViewProvider;
}
public void setTreeElementPointer(@Nullable FileElement element) {
updateTrees(FileTrees.noStub(element, this));
}
@RequiredReadAction
@Override
public PsiElement findElementAt(int offset) {
return getViewProvider().findElementAt(offset);
}
@RequiredReadAction
@Override
public PsiReference findReferenceAt(int offset) {
return getViewProvider().findReferenceAt(offset);
}
@RequiredReadAction
@Override
@NotNull
public char[] textToCharArray() {
return CharArrayUtil.fromSequence(getViewProvider().getContents());
}
@SuppressWarnings("unchecked")
@NotNull
public <T> T[] findChildrenByClass(Class<T> aClass) {
List<T> result = new ArrayList<>();
for (PsiElement child : getChildren()) {
if (aClass.isInstance(child)) result.add((T)child);
}
return result.toArray((T[]) Array.newInstance(aClass, result.size()));
}
@SuppressWarnings("unchecked")
@Nullable
public <T> T findChildByClass(Class<T> aClass) {
for (PsiElement child : getChildren()) {
if (aClass.isInstance(child)) return (T)child;
}
return null;
}
public boolean isTemplateDataFile() {
return false;
}
@Override
public PsiElement getContext() {
return FileContextUtil.getFileContext(this);
}
@Override
public void onContentReload() {
ApplicationManager.getApplication().assertWriteAccessAllowed();
DebugUtil.startPsiModification("onContentReload");
try {
myRefToPsi.invalidatePsi();
FileElement treeElement = derefTreeElement();
if (treeElement != null) {
setTreeElementPointer(null);
treeElement.detachFromFile();
DebugUtil.onInvalidated(treeElement);
}
updateTrees(myTrees.clearStub("onContentReload"));
}
finally {
DebugUtil.finishPsiModification();
}
clearCaches();
}
/**
* @return a root stub of {@link #getStubTree()}, or null if the file is not stub-based or AST has been loaded.
*/
@Nullable
public StubElement getStub() {
StubTree stubHolder = getStubTree();
return stubHolder != null ? stubHolder.getRoot() : null;
}
/**
* A green stub is a stub object that can co-exist with tree (AST). So, contrary to {@link #getStub()}, can be non-null
* even if the AST has been loaded in this file. It can be used in cases when retrieving information from a stub is cheaper
* than from AST.
* @return a stub object corresponding to the file's content, or null if it's not available (e.g. has been garbage-collected)
* @see #getStub()
* @see #getStubTree()
*/
@Nullable
public final StubElement getGreenStub() {
StubTree stubHolder = getGreenStubTree();
return stubHolder != null ? stubHolder.getRoot() : null;
}
/**
* @return a stub tree, if this file has it, and only if AST isn't loaded
*/
@RequiredReadAction
@Override
@Nullable
public StubTree getStubTree() {
ApplicationManager.getApplication().assertReadAccessAllowed();
if (myTrees.astLoaded && !mayReloadStub()) return null;
if (Boolean.TRUE.equals(getUserData(BUILDING_STUB))) return null;
final StubTree derefd = derefStub();
if (derefd != null) return derefd;
if (getElementTypeForStubBuilder() == null) return null;
final VirtualFile vFile = getVirtualFile();
if (!(vFile instanceof VirtualFileWithId) || !vFile.isValid()) return null;
ObjectStubTree tree = StubTreeLoader.getInstance().readOrBuild(getProject(), vFile, this);
if (!(tree instanceof StubTree)) return null;
final FileViewProvider viewProvider = getViewProvider();
final List<Pair<IStubFileElementType, PsiFile>> roots = StubTreeBuilder.getStubbedRoots(viewProvider);
synchronized (PsiLock.LOCK) {
if (getTreeElement() != null || hasUnbindableCachedPsi()) return null;
final StubTree derefdOnLock = derefStub();
if (derefdOnLock != null) return derefdOnLock;
PsiFileStub baseRoot = ((StubTree)tree).getRoot();
if (baseRoot instanceof PsiFileStubImpl && !((PsiFileStubImpl)baseRoot).rootsAreSet()) {
LOG.error("Stub roots must be set when stub tree was read or built with StubTreeLoader");
return null;
}
final PsiFileStub[] stubRoots = baseRoot.getStubRoots();
if (stubRoots.length != roots.size()) {
final Function<PsiFileStub, String> stubToString = stub -> stub.getClass().getSimpleName();
LOG.error("readOrBuilt roots = " + StringUtil.join(stubRoots, stubToString, ", ") + "; " +
StubTreeLoader.getFileViewProviderMismatchDiagnostics(viewProvider));
rebuildStub();
return null;
}
// first, set all references from stubs to existing PSI (in AST or AstPathPsiMap)
Map<PsiFileImpl, StubTree> bindings = prepareAllStubTrees(roots, stubRoots);
StubTree result = bindings.get(this);
assert result != null : "Current file not in root list: " + roots + ", vp=" + viewProvider;
// now stubs can be safely published
for (PsiFileImpl eachPsiRoot : bindings.keySet()) {
eachPsiRoot.updateTrees(eachPsiRoot.myTrees.withExclusiveStub(bindings.get(eachPsiRoot), bindings.keySet()));
}
return result;
}
}
private static Map<PsiFileImpl, StubTree> prepareAllStubTrees(List<Pair<IStubFileElementType, PsiFile>> roots, PsiFileStub[] rootStubs) {
Map<PsiFileImpl, StubTree> bindings = ContainerUtil.newIdentityHashMap();
for (int i = 0; i < roots.size(); i++) {
PsiFileImpl eachPsiRoot = (PsiFileImpl)roots.get(i).second;
//noinspection unchecked
((StubBase)rootStubs[i]).setPsi(eachPsiRoot);
StubTree stubTree = new StubTree(rootStubs[i]);
FileElement fileElement = eachPsiRoot.getTreeElement();
stubTree.setDebugInfo("created in getStubTree(), with AST = " + (fileElement != null));
if (fileElement != null) {
// Set references from these stubs to AST, because:
// Stub index might call getStubTree on main PSI file, but then use getPlainListFromAllRoots and return stubs from another file.
// Even if that file already has AST, stub.getPsi() should be the same as in AST
TreeUtil.bindStubsToTree(eachPsiRoot, stubTree, fileElement);
} else {
eachPsiRoot.bindStubsToCachedPsi(stubTree);
bindings.put(eachPsiRoot, stubTree);
}
}
return bindings;
}
private boolean mayReloadStub() {
StubTreeLoader loader = StubTreeLoader.getInstance();
if (loader != null && loader.isStubReloadingProhibited()) {
return false;
}
return getTreeElement() == null && !useStrongRefs() && !hasUnbindableCachedPsi();
}
private boolean hasUnbindableCachedPsi() {
for (PsiFile file : myViewProvider.getAllFiles()) {
if (file instanceof PsiFileImpl) {
for (StubBasedPsiElementBase<?> psi : ((PsiFileImpl)file).myRefToPsi.getAllCachedPsi()) {
if (psi.getStubIndex() < 0) {
return true;
}
}
}
}
return false;
}
@Nullable
private StubTree derefStub() {
return myTrees.derefStub();
}
private void updateTrees(@NotNull FileTrees trees) {
if (!ourTreeUpdater.compareAndSet(this, myTrees, trees)) {
LOG.error("Non-atomic trees update");
myTrees = trees;
}
}
private void bindStubsToCachedPsi(StubTree stubTree) {
for (StubBasedPsiElementBase<?> psi : myRefToPsi.getAllCachedPsi()) {
int index = psi.getStubIndex();
if (index >= 0) {
//noinspection unchecked
((StubBase)stubTree.getPlainList().get(index)).setPsi(psi);
}
}
}
protected PsiFileImpl cloneImpl(FileElement treeElementClone) {
PsiFileImpl clone = (PsiFileImpl)super.clone();
clone.myRefToPsi = new AstPathPsiMap(getProject());
clone.setTreeElementPointer(treeElementClone); // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963)
treeElementClone.setPsi(clone);
return clone;
}
private boolean isKeepTreeElementByHardReference() {
return !getViewProvider().isEventSystemEnabled();
}
@NotNull
private Getter<FileElement> createTreeElementPointer(@NotNull FileElement treeElement) {
if (isKeepTreeElementByHardReference()) {
return treeElement;
}
return myManager.isBatchFilesProcessingMode()
? new PatchedWeakReference<>(treeElement)
: new SoftReference<>(treeElement);
}
@Override
public final PsiManager getManager() {
return myManager;
}
@Override
public PsiElement getNavigationElement() {
return this;
}
@Override
public PsiElement getOriginalElement() {
return getOriginalFile();
}
@NotNull
public final FileElement calcTreeElement() {
// Attempt to find (loaded) tree element without taking lock first.
FileElement treeElement = getTreeElement();
if (treeElement != null) return treeElement;
treeElement = myFileElementBeingLoaded.get();
if (treeElement != null) return treeElement;
return loadTreeElement();
}
@RequiredReadAction
@Override
@NotNull
public PsiElement[] getChildren() {
return calcTreeElement().getChildrenAsPsiElements((TokenSet)null, PsiElement.ARRAY_FACTORY);
}
@RequiredReadAction
@Override
public PsiElement getFirstChild() {
return SharedImplUtil.getFirstChild(getNode());
}
@RequiredReadAction
@Override
public PsiElement getLastChild() {
return SharedImplUtil.getLastChild(getNode());
}
@Override
public void acceptChildren(@NotNull PsiElementVisitor visitor) {
SharedImplUtil.acceptChildren(visitor, getNode());
}
@RequiredReadAction
@Override
public int getStartOffsetInParent() {
return calcTreeElement().getStartOffsetInParent();
}
@Override
public int getTextOffset() {
return calcTreeElement().getTextOffset();
}
@Override
public boolean textMatches(@NotNull CharSequence text) {
return calcTreeElement().textMatches(text);
}
@Override
public boolean textMatches(@NotNull PsiElement element) {
return calcTreeElement().textMatches(element);
}
@RequiredReadAction
@Override
public boolean textContains(char c) {
return calcTreeElement().textContains(c);
}
@Override
public final PsiElement copy() {
return clone();
}
@Override
public PsiElement add(@NotNull PsiElement element) throws IncorrectOperationException {
CheckUtil.checkWritable(this);
TreeElement elementCopy = ChangeUtil.copyToElement(element);
calcTreeElement().addInternal(elementCopy, elementCopy, null, null);
elementCopy = ChangeUtil.decodeInformation(elementCopy);
return SourceTreeToPsiMap.treeElementToPsi(elementCopy);
}
@Override
public PsiElement addBefore(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException {
CheckUtil.checkWritable(this);
TreeElement elementCopy = ChangeUtil.copyToElement(element);
calcTreeElement().addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.TRUE);
elementCopy = ChangeUtil.decodeInformation(elementCopy);
return SourceTreeToPsiMap.treeElementToPsi(elementCopy);
}
@Override
public PsiElement addAfter(@NotNull PsiElement element, PsiElement anchor) throws IncorrectOperationException {
CheckUtil.checkWritable(this);
TreeElement elementCopy = ChangeUtil.copyToElement(element);
calcTreeElement().addInternal(elementCopy, elementCopy, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE);
elementCopy = ChangeUtil.decodeInformation(elementCopy);
return SourceTreeToPsiMap.treeElementToPsi(elementCopy);
}
@Override
public final void checkAdd(@NotNull PsiElement element) throws IncorrectOperationException {
CheckUtil.checkWritable(this);
}
@Override
public PsiElement addRange(PsiElement first, PsiElement last) throws IncorrectOperationException {
return SharedImplUtil.addRange(this, first, last, null, null);
}
@Override
public PsiElement addRangeBefore(@NotNull PsiElement first, @NotNull PsiElement last, PsiElement anchor)
throws IncorrectOperationException {
return SharedImplUtil.addRange(this, first, last, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.TRUE);
}
@Override
public PsiElement addRangeAfter(PsiElement first, PsiElement last, PsiElement anchor)
throws IncorrectOperationException {
return SharedImplUtil.addRange(this, first, last, SourceTreeToPsiMap.psiElementToTree(anchor), Boolean.FALSE);
}
@Override
public void deleteChildRange(PsiElement first, PsiElement last) throws IncorrectOperationException {
CheckUtil.checkWritable(this);
if (first == null) {
LOG.assertTrue(last == null);
return;
}
ASTNode firstElement = SourceTreeToPsiMap.psiElementToTree(first);
ASTNode lastElement = SourceTreeToPsiMap.psiElementToTree(last);
CompositeElement treeElement = calcTreeElement();
LOG.assertTrue(firstElement.getTreeParent() == treeElement);
LOG.assertTrue(lastElement.getTreeParent() == treeElement);
CodeEditUtil.removeChildren(treeElement, firstElement, lastElement);
}
@Override
public PsiElement replace(@NotNull PsiElement newElement) throws IncorrectOperationException {
CompositeElement treeElement = calcTreeElement();
return SharedImplUtil.doReplace(this, treeElement, newElement);
}
@Override
public PsiReference getReference() {
return null;
}
@Override
@NotNull
public PsiReference[] getReferences() {
return SharedPsiElementImplUtil.getReferences(this);
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
return true;
}
@Override
@NotNull
public GlobalSearchScope getResolveScope() {
return ResolveScopeManager.getElementResolveScope(this);
}
@Override
@NotNull
public SearchScope getUseScope() {
return ResolveScopeManager.getElementUseScope(this);
}
@Override
public ItemPresentation getPresentation() {
return new ItemPresentation() {
@Override
public String getPresentableText() {
return getName();
}
@Override
public String getLocationString() {
final PsiDirectory psiDirectory = getParent();
if (psiDirectory != null) {
return psiDirectory.getVirtualFile().getPresentableUrl();
}
return null;
}
@Override
public Icon getIcon(final boolean open) {
return IconDescriptorUpdaters.getIcon(PsiFileImpl.this, 0);
}
};
}
@Override
public void navigate(boolean requestFocus) {
assert canNavigate() : this;
//noinspection ConstantConditions
PsiNavigationSupport.getInstance().getDescriptor(this).navigate(requestFocus);
}
@Override
public boolean canNavigate() {
return PsiNavigationSupport.getInstance().canNavigate(this);
}
@Override
public boolean canNavigateToSource() {
return canNavigate();
}
@Override
@NotNull
public final Project getProject() {
return getManager().getProject();
}
@NotNull
@Override
public FileASTNode getNode() {
return calcTreeElement();
}
@Override
public boolean isEquivalentTo(final PsiElement another) {
return this == another;
}
private final Object myStubFromTreeLock = new Object();
/**
* @return a stub tree object having {@link #getGreenStub()} as a root, or null if there's no green stub available
*/
@Nullable
public final StubTree getGreenStubTree() {
StubTree result = derefStub();
return result != null ? result : getStubTree();
}
@NotNull
public StubTree calcStubTree() {
StubTree tree = derefStub();
if (tree != null) {
return tree;
}
assert myFileElementBeingLoaded.get() == null : "non-empty thread-local";
FileElement fileElement = calcTreeElement();
synchronized (myStubFromTreeLock) {
tree = derefStub();
if (tree == null) {
ApplicationManager.getApplication().assertReadAccessAllowed();
IStubFileElementType contentElementType = getElementTypeForStubBuilder();
if (contentElementType == null) {
VirtualFile vFile = getVirtualFile();
String message = "ContentElementType: " + getContentElementType() + "; file: " + this +
"\n\t" + "Boolean.TRUE.equals(getUserData(BUILDING_STUB)) = " + Boolean.TRUE.equals(getUserData(BUILDING_STUB)) +
"\n\t" + "getTreeElement() = " + getTreeElement() +
"\n\t" + "vFile instanceof VirtualFileWithId = " + (vFile instanceof VirtualFileWithId) +
"\n\t" + "StubUpdatingIndex.canHaveStub(vFile) = " + StubTreeLoader.getInstance().canHaveStub(vFile);
rebuildStub();
throw new AssertionError(message);
}
StubElement currentStubTree = contentElementType.getBuilder().buildStubTree(this);
if (currentStubTree == null) {
throw new AssertionError("Stub tree wasn't built for " + contentElementType + "; file: " + this);
}
tree = new StubTree((PsiFileStub)currentStubTree);
tree.setDebugInfo("created in calcStubTree");
try {
TreeUtil.bindStubsToTree(this, tree, fileElement);
}
catch (TreeUtil.StubBindingException e) {
rebuildStub();
throw new RuntimeException("Stub and PSI element type mismatch in " + getName(), e);
}
updateTrees(myTrees.withGreenStub(tree, this));
}
return tree;
}
}
private void rebuildStub() {
ApplicationManager.getApplication().invokeLater(() -> {
if (!myManager.isDisposed()) {
myManager.dropResolveCaches();
((PsiModificationTrackerImpl)myManager.getModificationTracker()).incCounter();
}
final VirtualFile vFile = getVirtualFile();
if (vFile != null && vFile.isValid()) {
final Document doc = FileDocumentManager.getInstance().getCachedDocument(vFile);
if (doc != null) {
FileDocumentManager.getInstance().saveDocument(doc);
}
FileContentUtilCore.reparseFiles(vFile);
StubTreeLoader.getInstance().rebuildStubTree(vFile);
}
}, ModalityState.NON_MODAL);
}
@Override
public void putInfo(@NotNull Map<String, String> info) {
putInfo(this, info);
}
public static void putInfo(PsiFile psiFile, Map<String, String> info) {
info.put("fileName", psiFile.getName());
info.put("fileType", psiFile.getFileType().toString());
}
@Override
public String toString() {
return myElementType.toString();
}
public final void beforeAstChange() {
if (!useStrongRefs()) {
synchronized (PsiLock.LOCK) {
for (PsiFile root : myViewProvider.getAllFiles()) {
if ((root instanceof PsiFileImpl)) {
((PsiFileImpl)root).switchToStrongRefs();
}
}
}
}
}
private void switchToStrongRefs() {
FileElement node = calcTreeElement();
updateTrees(myTrees.switchToStrongRefs());
myRefToPsi.switchToStrongRefs();
AstPath.invalidatePaths(node);
}
@Nullable
public StubBasedPsiElementBase<?> obtainPsi(@NotNull AstPath path, @NotNull Factory<StubBasedPsiElementBase<?>> creator) {
if (useStrongRefs()) {
return null;
}
StubBasedPsiElementBase<?> psi = myRefToPsi.getCachedPsi(path);
if (psi != null) return psi;
synchronized (PsiLock.LOCK) {
psi = myRefToPsi.getCachedPsi(path);
return psi != null ? psi : myRefToPsi.cachePsi(path, creator.create());
}
}
final AstPathPsiMap getRefToPsi() {
return myRefToPsi;
}
public final boolean useStrongRefs() {
return myTrees.useStrongRefs;
}
public boolean mayCacheAst() {
return myFileElementBeingLoaded.get() == null;
}
}