blob: dabb45cd7667a096e434497489b6814669e60e89 [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;
import com.intellij.extapi.psi.StubBasedPsiElementBase;
import com.intellij.ide.util.PsiNavigationSupport;
import com.intellij.lang.ASTFactory;
import com.intellij.lang.ASTNode;
import com.intellij.lang.FileASTNode;
import com.intellij.lang.Language;
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.Getter;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
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.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.IElementType;
import com.intellij.psi.tree.ILazyParseableElementType;
import com.intellij.psi.tree.IStubFileElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.reference.SoftReference;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PatchedWeakReference;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.lang.ref.Reference;
import java.lang.reflect.Array;
import java.util.*;
public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiFileWithStubSupport, Queryable {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.PsiFileImpl");
private IElementType myElementType;
protected IElementType myContentElementType;
private long myModificationStamp;
protected PsiFile myOriginalFile = null;
private final FileViewProvider myViewProvider;
private volatile Reference<StubTree> myStub;
protected final PsiManagerEx myManager;
private volatile Getter<FileElement> myTreeElementPointer; // SoftReference/WeakReference to ASTNode or a strong reference to a tree if the file is a DummyHolder
public static final Key<Boolean> BUILDING_STUB = new Key<Boolean>("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;
}
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;
}
public FileElement getTreeElement() {
FileElement node = derefTreeElement();
if (node != null) return node;
if (!getViewProvider().isPhysical()) {
return loadTreeElement();
}
synchronized (PsiLock.LOCK) {
return derefTreeElement();
}
}
private FileElement derefTreeElement() {
Getter<FileElement> pointer = myTreeElementPointer;
FileElement treeElement = SoftReference.deref(pointer);
if (treeElement != null) return treeElement;
synchronized (PsiLock.LOCK) {
if (myTreeElementPointer == pointer) {
myTreeElementPointer = null;
}
}
return null;
}
@Override
public VirtualFile getVirtualFile() {
return getViewProvider().isEventSystemEnabled() ? getViewProvider().getVirtualFile() : null;
}
@Override
public boolean processChildren(final PsiElementProcessor<PsiFileSystemItem> processor) {
return true;
}
@Override
public boolean isValid() {
FileViewProvider provider = getViewProvider();
final VirtualFile vFile = provider.getVirtualFile();
if (!vFile.isValid()) return false;
if (!provider.isEventSystemEnabled()) return true; // "dummy" file
if (myManager.getProject().isDisposed()) return false;
return isPsiUpToDate(vFile);
}
protected boolean isPsiUpToDate(@NotNull VirtualFile vFile) {
final FileViewProvider provider = myManager.findViewProvider(vFile);
Language language = getLanguage();
if (provider == null || provider.getPsi(language) == this) { // provider == null in tests
return true;
}
Language baseLanguage = provider.getBaseLanguage();
return baseLanguage != language && provider.getPsi(baseLanguage) == 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()+"'");
}
Document cachedDocument = FileDocumentManager.getInstance().getCachedDocument(getViewProvider().getVirtualFile());
FileElement treeElement = createFileElement(viewProvider.getContents());
treeElement.setPsi(this);
List<Pair<StubBasedPsiElementBase, CompositeElement>> bindings = calcStubAstBindings(treeElement, cachedDocument);
synchronized (PsiLock.LOCK) {
FileElement existing = derefTreeElement();
if (existing != null) {
return existing;
}
switchFromStubToAst(bindings);
myStub = null;
myTreeElementPointer = createTreeElementPointer(treeElement);
if (LOG.isDebugEnabled() && viewProvider.isPhysical()) {
LOG.debug("Loaded text for file " + viewProvider.getVirtualFile().getPresentableUrl());
}
return treeElement;
}
}
@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 static void switchFromStubToAst(List<Pair<StubBasedPsiElementBase, CompositeElement>> pairs) {
for (Pair<StubBasedPsiElementBase, CompositeElement> pair : pairs) {
pair.second.setPsi(pair.first);
pair.first.setNode(pair.second);
pair.first.setStub(null);
}
}
private List<Pair<StubBasedPsiElementBase, CompositeElement>> calcStubAstBindings(final ASTNode root, final Document cachedDocument) {
final StubTree stubTree = derefStub();
if (stubTree == null) {
return Collections.emptyList();
}
final Iterator<StubElement<?>> stubs = stubTree.getPlainList().iterator();
stubs.next(); // Skip file stub;
final List<Pair<StubBasedPsiElementBase, CompositeElement>> result = ContainerUtil.newArrayList();
final StubBuilder builder = ((IStubFileElementType)getContentElementType()).getBuilder();
LazyParseableElement.setSuppressEagerPsiCreation(true);
try {
((TreeElement)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, cachedDocument);
}
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, cachedDocument);
}
PsiElement psi = stub.getPsi();
assert psi != null : "Stub " + stub + " (" + stub.getClass() + ") has returned null PSI";
result.add(Pair.create((StubBasedPsiElementBase)psi, (CompositeElement)node));
}
super.visitNode(node);
}
});
}
finally {
LazyParseableElement.setSuppressEagerPsiCreation(false);
}
if (stubs.hasNext()) {
reportStubAstMismatch("Stub list in " + getName() + " has more elements than PSI", stubTree, cachedDocument);
}
return result;
}
protected void reportStubAstMismatch(String message, StubTree stubTree, Document cachedDocument) {
rebuildStub();
clearStub("stub-psi mismatch");
scheduleDropCachesWithInvalidStubPsi();
String msg = message;
msg += "\n file=" + this;
msg += ", modStamp=" + getModificationStamp();
msg += "\n stub debugInfo=" + stubTree.getDebugInfo();
msg += "\n document before=" + cachedDocument;
ObjectStubTree latestIndexedStub = StubTreeLoader.getInstance().readFromVFile(getProject(), getVirtualFile());
msg += "\nlatestIndexedStub=" + latestIndexedStub;
if (latestIndexedStub != null) {
msg += "\n same size=" + (stubTree.getPlainList().size() == latestIndexedStub.getPlainList().size());
msg += "\n debugInfo=" + latestIndexedStub.getDebugInfo();
}
FileViewProvider viewProvider = getViewProvider();
msg += "\n viewProvider=" + viewProvider;
msg += "\n viewProvider stamp: " + viewProvider.getModificationStamp();
VirtualFile file = viewProvider.getVirtualFile();
msg += "; file stamp: " + file.getModificationStamp();
msg += "; file modCount: " + file.getModificationCount();
Document document = FileDocumentManager.getInstance().getCachedDocument(file);
if (document != null) {
msg += "\n doc saved: " + !FileDocumentManager.getInstance().isDocumentUnsaved(document);
msg += "; doc stamp: " + document.getModificationStamp();
msg += "; doc size: " + document.getTextLength();
msg += "; committed: " + PsiDocumentManager.getInstance(getProject()).isCommitted(document);
}
throw new AssertionError(msg + "\n------------\n");
}
private void scheduleDropCachesWithInvalidStubPsi() {
// invokeLater even if already on EDT, because
// we might be inside an index query and write actions might result in deadlocks there (http://youtrack.jetbrains.com/issue/IDEA-123118)
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
((PsiModificationTrackerImpl)getManager().getModificationTracker()).incCounter();
}
});
}
});
}
@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 unloadContent() {
ApplicationManager.getApplication().assertWriteAccessAllowed();
clearCaches();
myViewProvider.beforeContentsSynchronized();
synchronized (PsiLock.LOCK) {
FileElement treeElement = derefTreeElement();
DebugUtil.startPsiModification("unloadContent");
try {
if (treeElement != null) {
myTreeElementPointer = null;
treeElement.detachFromFile();
DebugUtil.onInvalidated(treeElement);
}
clearStub("unloadContent");
}
finally {
DebugUtil.finishPsiModification();
}
}
}
private void clearStub(@NotNull String reason) {
StubTree stubHolder = SoftReference.dereference(myStub);
if (stubHolder != null) {
((PsiFileStubImpl<?>)stubHolder.getRoot()).clearPsi(reason);
}
myStub = null;
}
public void clearCaches() {
myModificationStamp ++;
}
@Override
public String getText() {
return getViewProvider().getContents().toString();
}
@Override
public int getTextLength() {
final ASTNode tree = derefTreeElement();
if (tree != null) return tree.getTextLength();
return getViewProvider().getContents().length();
}
@Override
public TextRange getTextRange() {
return new TextRange(0, getTextLength());
}
@Override
public PsiElement getNextSibling() {
return SharedPsiElementImplUtil.getNextSibling(this);
}
@Override
public PsiElement getPrevSibling() {
return SharedPsiElementImplUtil.getPrevSibling(this);
}
@Override
public long getModificationStamp() {
return myModificationStamp;
}
@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) {
clearStub(reason);
}
if (tree != null) {
tree.putUserData(STUB_TREE_IN_PARSED_TREE, null);
}
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);
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;
}
return clone;
}
@Override
@NotNull public String getName() {
return getViewProvider().getVirtualFile().getName();
}
@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() {
final VirtualFile parentFile = getViewProvider().getVirtualFile().getParent();
if (parentFile == null) 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 = new Comparator<PsiFile>() {
@Override
public int compare(PsiFile o1, PsiFile o2) {
return o1.getLanguage().getID().compareTo(o2.getLanguage().getID());
}
};
@Override
public boolean isPhysical() {
// TODO[ik] remove this shit with dummy file system
return getViewProvider().isEventSystemEnabled();
}
@Override
@NotNull
public Language getLanguage() {
return myElementType.getLanguage();
}
@Override
@NotNull
public FileViewProvider getViewProvider() {
return myViewProvider;
}
public void setTreeElementPointer(FileElement element) {
myTreeElementPointer = element;
}
@Override
public PsiElement findElementAt(int offset) {
return getViewProvider().findElementAt(offset);
}
@Override
public PsiReference findReferenceAt(int offset) {
return getViewProvider().findReferenceAt(offset);
}
@Override
@NotNull
public char[] textToCharArray() {
return CharArrayUtil.fromSequence(getViewProvider().getContents());
}
@NotNull
public <T> T[] findChildrenByClass(Class<T> aClass) {
List<T> result = new ArrayList<T>();
for (PsiElement child : getChildren()) {
if (aClass.isInstance(child)) result.add((T)child);
}
return result.toArray((T[]) Array.newInstance(aClass, result.size()));
}
@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() {
subtreeChanged(); // important! otherwise cached information is not released
if (isContentsLoaded()) {
unloadContent();
}
}
@Nullable
public StubElement getStub() {
StubTree stubHolder = getStubTree();
return stubHolder != null ? stubHolder.getRoot() : null;
}
@Override
@Nullable
public StubTree getStubTree() {
ApplicationManager.getApplication().assertReadAccessAllowed();
if (Boolean.TRUE.equals(getUserData(BUILDING_STUB))) return null;
final StubTree derefd = derefStub();
if (derefd != null) return derefd;
if (getTreeElement() != null) return null;
if (!(getContentElementType() instanceof IStubFileElementType)) return null;
final VirtualFile vFile = getVirtualFile();
if (!(vFile instanceof VirtualFileWithId)) return null;
final PsiFile stubBindingRoot = getViewProvider().getStubBindingRoot();
if (stubBindingRoot != this) {
LOG.error("Attempted to create stubs for non-root file: " + this + ", stub binding root: " + stubBindingRoot);
return null;
}
ObjectStubTree tree = StubTreeLoader.getInstance().readOrBuild(getProject(), vFile, this);
if (!(tree instanceof StubTree)) return null;
StubTree stubHolder = (StubTree)tree;
synchronized (PsiLock.LOCK) {
if (getTreeElement() != null) return null;
final StubTree derefdOnLock = derefStub();
if (derefdOnLock != null) return derefdOnLock;
//noinspection unchecked
((StubBase)stubHolder.getRoot()).setPsi(this);
myStub = new SoftReference<StubTree>(stubHolder);
return stubHolder;
}
}
@Nullable
private StubTree derefStub() {
if (myStub == null) return null;
synchronized (PsiLock.LOCK) {
return SoftReference.dereference(myStub);
}
}
protected PsiFileImpl cloneImpl(FileElement treeElementClone) {
PsiFileImpl clone = (PsiFileImpl)super.clone();
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<FileElement>(treeElement)
: new SoftReference<FileElement>(treeElement);
}
@Override
public PsiManager getManager() {
return myManager;
}
@Override
public PsiElement getNavigationElement() {
return this;
}
@Override
public PsiElement getOriginalElement() {
return this;
}
@NotNull
public final FileElement calcTreeElement() {
// Attempt to find (loaded) tree element without taking lock first.
FileElement treeElement = getTreeElement();
if (treeElement != null) return treeElement;
return loadTreeElement();
}
@Override
@NotNull
public PsiElement[] getChildren() {
return calcTreeElement().getChildrenAsPsiElements((TokenSet)null, PsiElement.ARRAY_FACTORY);
}
@Override
public PsiElement getFirstChild() {
return SharedImplUtil.getFirstChild(calcTreeElement());
}
@Override
public PsiElement getLastChild() {
return SharedImplUtil.getLastChild(calcTreeElement());
}
@Override
public void acceptChildren(@NotNull PsiElementVisitor visitor) {
SharedImplUtil.acceptChildren(visitor, calcTreeElement());
}
@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);
}
@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 PsiFileImpl.this.getIcon(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 Project getProject() {
final PsiManager manager = getManager();
if (manager == null) throw new PsiInvalidElementAccessException(this);
return manager.getProject();
}
@NotNull
@Override
public FileASTNode getNode() {
return calcTreeElement();
}
@Override
public boolean isEquivalentTo(final PsiElement another) {
return this == another;
}
private static final Key<SoftReference<StubTree>> STUB_TREE_IN_PARSED_TREE = Key.create("STUB_TREE_IN_PARSED_TREE");
private final Object myStubFromTreeLock = new Object();
public StubTree calcStubTree() {
FileElement fileElement = calcTreeElement();
synchronized (myStubFromTreeLock) {
SoftReference<StubTree> ref = fileElement.getUserData(STUB_TREE_IN_PARSED_TREE);
StubTree tree = SoftReference.dereference(ref);
if (tree == null) {
ApplicationManager.getApplication().assertReadAccessAllowed();
IElementType contentElementType = getContentElementType();
if (!(contentElementType instanceof IStubFileElementType)) {
VirtualFile vFile = getVirtualFile();
String message = "ContentElementType: " + contentElementType + "; 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 = ((IStubFileElementType)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);
}
catch (TreeUtil.StubBindingException e) {
rebuildStub();
throw new RuntimeException("Stub and PSI element type mismatch in " + getName(), e);
}
fileElement.putUserData(STUB_TREE_IN_PARSED_TREE, new SoftReference<StubTree>(tree));
}
return tree;
}
}
private void rebuildStub() {
final VirtualFile vFile = getVirtualFile();
if (vFile != null && vFile.isValid()) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
final Document doc = FileDocumentManager.getInstance().getCachedDocument(vFile);
if (doc != null) {
FileDocumentManager.getInstance().saveDocument(doc);
}
}
}, ModalityState.NON_MODAL);
StubTreeLoader.getInstance().rebuildStubTree(vFile);
}
}
@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();
}
}