| /* |
| * 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; |
| |
| import com.intellij.lang.Language; |
| import com.intellij.lang.LanguageParserDefinitions; |
| import com.intellij.lang.ParserDefinition; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.undo.UndoConstants; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.fileEditor.impl.LoadTextUtil; |
| import com.intellij.openapi.fileTypes.*; |
| import com.intellij.openapi.progress.ProcessCanceledException; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.FileIndexFacade; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.UserDataHolderBase; |
| import com.intellij.openapi.vfs.NonPhysicalFileSystem; |
| import com.intellij.openapi.vfs.PersistentFSConstants; |
| import com.intellij.openapi.vfs.VFileProperty; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.impl.PsiManagerEx; |
| import com.intellij.psi.impl.PsiManagerImpl; |
| import com.intellij.psi.impl.file.PsiBinaryFileImpl; |
| import com.intellij.psi.impl.file.PsiLargeFileImpl; |
| import com.intellij.psi.impl.file.impl.FileManager; |
| import com.intellij.psi.impl.source.PsiFileImpl; |
| import com.intellij.psi.impl.source.PsiPlainTextFileImpl; |
| import com.intellij.psi.impl.source.tree.FileElement; |
| import com.intellij.testFramework.LightVirtualFile; |
| import com.intellij.util.LocalTimeCounter; |
| import com.intellij.util.ReflectionUtil; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.lang.ref.Reference; |
| import java.lang.ref.SoftReference; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| public class SingleRootFileViewProvider extends UserDataHolderBase implements FileViewProvider { |
| private static final Key<Boolean> OUR_NO_SIZE_LIMIT_KEY = Key.create("no.size.limit"); |
| private static final Logger LOG = Logger.getInstance("#" + SingleRootFileViewProvider.class.getCanonicalName()); |
| @NotNull private final PsiManager myManager; |
| @NotNull private final VirtualFile myVirtualFile; |
| private final boolean myEventSystemEnabled; |
| private final boolean myPhysical; |
| private final AtomicReference<PsiFile> myPsiFile = new AtomicReference<PsiFile>(); |
| private volatile Content myContent; |
| private volatile Reference<Document> myDocument; |
| @NotNull private final Language myBaseLanguage; |
| |
| public SingleRootFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile file) { |
| this(manager, file, true); |
| } |
| |
| public SingleRootFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile virtualFile, final boolean eventSystemEnabled) { |
| this(manager, virtualFile, eventSystemEnabled, calcBaseLanguage(virtualFile, manager.getProject())); |
| } |
| |
| protected SingleRootFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile virtualFile, final boolean eventSystemEnabled, @NotNull Language language) { |
| myManager = manager; |
| myVirtualFile = virtualFile; |
| myEventSystemEnabled = eventSystemEnabled; |
| myBaseLanguage = language; |
| setContent(new VirtualFileContent()); |
| myPhysical = isEventSystemEnabled() && |
| !(virtualFile instanceof LightVirtualFile) && |
| !(virtualFile.getFileSystem() instanceof NonPhysicalFileSystem); |
| } |
| |
| @Override |
| @NotNull |
| public Language getBaseLanguage() { |
| return myBaseLanguage; |
| } |
| |
| private static Language calcBaseLanguage(@NotNull VirtualFile file, @NotNull Project project) { |
| if (file instanceof LightVirtualFile) { |
| final Language language = ((LightVirtualFile)file).getLanguage(); |
| if (language != null) { |
| return language; |
| } |
| } |
| |
| FileType fileType = file.getFileType(); |
| if (fileType.isBinary()) return Language.ANY; |
| if (isTooLargeForIntelligence(file)) return PlainTextLanguage.INSTANCE; |
| |
| if (fileType instanceof LanguageFileType) { |
| return LanguageSubstitutors.INSTANCE.substituteLanguage(((LanguageFileType)fileType).getLanguage(), file, project); |
| } |
| |
| final ContentBasedFileSubstitutor[] processors = Extensions.getExtensions(ContentBasedFileSubstitutor.EP_NAME); |
| for (ContentBasedFileSubstitutor processor : processors) { |
| Language language = processor.obtainLanguageForFile(file); |
| if (language != null) return language; |
| } |
| |
| return PlainTextLanguage.INSTANCE; |
| } |
| |
| @Override |
| @NotNull |
| public Set<Language> getLanguages() { |
| return Collections.singleton(getBaseLanguage()); |
| } |
| |
| @Override |
| @Nullable |
| public final PsiFile getPsi(@NotNull Language target) { |
| if (!isPhysical()) { |
| FileManager fileManager = ((PsiManagerEx)myManager).getFileManager(); |
| VirtualFile virtualFile = getVirtualFile(); |
| if (fileManager.findCachedViewProvider(virtualFile) == null) { |
| fileManager.setViewProvider(virtualFile, this); |
| } |
| } |
| return getPsiInner(target); |
| } |
| |
| @Override |
| @NotNull |
| public List<PsiFile> getAllFiles() { |
| return ContainerUtil.createMaybeSingletonList(getPsi(getBaseLanguage())); |
| } |
| |
| @Nullable |
| protected PsiFile getPsiInner(@NotNull Language target) { |
| if (target != getBaseLanguage()) { |
| return null; |
| } |
| PsiFile psiFile = myPsiFile.get(); |
| if (psiFile == null) { |
| psiFile = createFile(); |
| boolean set = myPsiFile.compareAndSet(null, psiFile); |
| if (!set) { |
| psiFile = myPsiFile.get(); |
| } |
| } |
| return psiFile; |
| } |
| |
| @Override |
| public void beforeContentsSynchronized() { |
| unsetPsiContent(); |
| } |
| |
| @Override |
| public void contentsSynchronized() { |
| unsetPsiContent(); |
| } |
| |
| private void unsetPsiContent() { |
| if (!(myContent instanceof PsiFileContent)) return; |
| final Document cachedDocument = getCachedDocument(); |
| setContent(cachedDocument == null ? new VirtualFileContent() : new DocumentContent()); |
| } |
| |
| public void beforeDocumentChanged(@Nullable PsiFile psiCause) { |
| PsiFile psiFile = psiCause != null ? psiCause : getPsi(getBaseLanguage()); |
| if (psiFile instanceof PsiFileImpl) { |
| setContent(new PsiFileContent((PsiFileImpl)psiFile, psiCause == null ? getModificationStamp() : LocalTimeCounter.currentTime())); |
| } |
| } |
| |
| @Override |
| public void rootChanged(@NotNull PsiFile psiFile) { |
| if (psiFile instanceof PsiFileImpl && ((PsiFileImpl)psiFile).isContentsLoaded()) { |
| setContent(new PsiFileContent((PsiFileImpl)psiFile, LocalTimeCounter.currentTime())); |
| } |
| } |
| |
| @Override |
| public boolean isEventSystemEnabled() { |
| return myEventSystemEnabled; |
| } |
| |
| @Override |
| public boolean isPhysical() { |
| return myPhysical; |
| } |
| |
| @Override |
| public long getModificationStamp() { |
| return getContent().getModificationStamp(); |
| } |
| |
| @Override |
| public boolean supportsIncrementalReparse(@NotNull final Language rootLanguage) { |
| return true; |
| } |
| |
| |
| public PsiFile getCachedPsi(@NotNull Language target) { |
| return myPsiFile.get(); |
| } |
| |
| @NotNull |
| public FileElement[] getKnownTreeRoots() { |
| PsiFile psiFile = myPsiFile.get(); |
| if (psiFile == null || !(psiFile instanceof PsiFileImpl)) return new FileElement[0]; |
| if (((PsiFileImpl)psiFile).getTreeElement() == null) return new FileElement[0]; |
| return new FileElement[]{(FileElement)psiFile.getNode()}; |
| } |
| |
| private PsiFile createFile() { |
| try { |
| final VirtualFile vFile = getVirtualFile(); |
| if (vFile.isDirectory()) return null; |
| if (isIgnored()) return null; |
| |
| final Project project = myManager.getProject(); |
| if (isPhysical() && vFile.isInLocalFileSystem()) { // check directories consistency |
| final VirtualFile parent = vFile.getParent(); |
| if (parent == null) return null; |
| final PsiDirectory psiDir = getManager().findDirectory(parent); |
| if (psiDir == null) { |
| FileIndexFacade indexFacade = FileIndexFacade.getInstance(project); |
| if (!indexFacade.isInLibrarySource(vFile) && !indexFacade.isInLibraryClasses(vFile)) { |
| return null; |
| } |
| } |
| } |
| |
| FileType fileType = vFile.getFileType(); |
| return createFile(project, vFile, fileType); |
| } |
| catch (ProcessCanceledException e) { |
| throw e; |
| } |
| catch (Throwable e) { |
| LOG.error(e); |
| return null; |
| } |
| } |
| |
| protected boolean isIgnored() { |
| final VirtualFile file = getVirtualFile(); |
| return !(file instanceof LightVirtualFile) && FileTypeRegistry.getInstance().isFileIgnored(file); |
| } |
| |
| @Nullable |
| protected PsiFile createFile(@NotNull Project project, @NotNull VirtualFile file, @NotNull FileType fileType) { |
| if (fileType.isBinary() || file.is(VFileProperty.SPECIAL)) { |
| return new PsiBinaryFileImpl((PsiManagerImpl)getManager(), this); |
| } |
| if (!isTooLargeForIntelligence(file)) { |
| final PsiFile psiFile = createFile(getBaseLanguage()); |
| if (psiFile != null) return psiFile; |
| } |
| |
| if (isTooLargeForContentLoading(file)) { |
| return new PsiLargeFileImpl((PsiManagerImpl)getManager(), this); |
| } |
| |
| return new PsiPlainTextFileImpl(this); |
| } |
| |
| @SuppressWarnings("UnusedDeclaration") |
| @Deprecated |
| public static boolean isTooLarge(@NotNull VirtualFile vFile) { |
| return isTooLargeForIntelligence(vFile); |
| } |
| |
| public static boolean isTooLargeForIntelligence(@NotNull VirtualFile vFile) { |
| if (!checkFileSizeLimit(vFile)) return false; |
| return fileSizeIsGreaterThan(vFile, PersistentFSConstants.getMaxIntellisenseFileSize()); |
| } |
| |
| public static boolean isTooLargeForContentLoading(@NotNull VirtualFile vFile) { |
| if (!checkFileSizeLimit(vFile)) return false; |
| return fileSizeIsGreaterThan(vFile, PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD); |
| } |
| |
| private static boolean checkFileSizeLimit(@NotNull VirtualFile vFile) { |
| return !Boolean.TRUE.equals(vFile.getUserData(OUR_NO_SIZE_LIMIT_KEY)); |
| } |
| public static void doNotCheckFileSizeLimit(@NotNull VirtualFile vFile) { |
| vFile.putUserData(OUR_NO_SIZE_LIMIT_KEY, Boolean.TRUE); |
| } |
| |
| public static boolean isTooLargeForIntelligence(@NotNull VirtualFile vFile, final long contentSize) { |
| if (!checkFileSizeLimit(vFile)) return false; |
| return contentSize > PersistentFSConstants.getMaxIntellisenseFileSize(); |
| } |
| |
| private static boolean fileSizeIsGreaterThan(@NotNull VirtualFile vFile, final long maxBytes) { |
| if (vFile instanceof LightVirtualFile) { |
| // This is optimization in order to avoid conversion of [large] file contents to bytes |
| final int lengthInChars = ((LightVirtualFile)vFile).getContent().length(); |
| if (lengthInChars < maxBytes / 2) return false; |
| if (lengthInChars > maxBytes ) return true; |
| } |
| |
| return vFile.getLength() > maxBytes; |
| } |
| |
| @Nullable |
| protected PsiFile createFile(@NotNull Language lang) { |
| if (lang != getBaseLanguage()) return null; |
| final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang); |
| if (parserDefinition != null) { |
| return parserDefinition.createFile(this); |
| } |
| return null; |
| } |
| |
| @Override |
| @NotNull |
| public PsiManager getManager() { |
| return myManager; |
| } |
| |
| @Override |
| @NotNull |
| public CharSequence getContents() { |
| return getContent().getText(); |
| } |
| |
| @Override |
| @NotNull |
| public VirtualFile getVirtualFile() { |
| return myVirtualFile; |
| } |
| |
| @Nullable |
| private Document getCachedDocument() { |
| final Document document = com.intellij.reference.SoftReference.dereference(myDocument); |
| if (document != null) return document; |
| return FileDocumentManager.getInstance().getCachedDocument(getVirtualFile()); |
| } |
| |
| @Override |
| public Document getDocument() { |
| Document document = com.intellij.reference.SoftReference.dereference(myDocument); |
| if (document == null/* TODO[ik] make this change && isEventSystemEnabled()*/) { |
| document = FileDocumentManager.getInstance().getDocument(getVirtualFile()); |
| myDocument = document == null ? null : new SoftReference<Document>(document); |
| } |
| if (document != null && getContent() instanceof VirtualFileContent) { |
| setContent(new DocumentContent()); |
| } |
| return document; |
| } |
| |
| @Override |
| public FileViewProvider clone() { |
| final VirtualFile origFile = getVirtualFile(); |
| LightVirtualFile copy = new LightVirtualFile(origFile.getName(), origFile.getFileType(), getContents(), origFile.getCharset(), getModificationStamp()); |
| copy.setOriginalFile(origFile); |
| copy.putUserData(UndoConstants.DONT_RECORD_UNDO, Boolean.TRUE); |
| copy.setCharset(origFile.getCharset()); |
| return createCopy(copy); |
| } |
| |
| @NotNull |
| @Override |
| public SingleRootFileViewProvider createCopy(@NotNull final VirtualFile copy) { |
| return new SingleRootFileViewProvider(getManager(), copy, false, myBaseLanguage); |
| } |
| |
| @Override |
| public PsiReference findReferenceAt(final int offset) { |
| final PsiFile psiFile = getPsi(getBaseLanguage()); |
| return findReferenceAt(psiFile, offset); |
| } |
| |
| @Override |
| public PsiElement findElementAt(final int offset, @NotNull final Language language) { |
| final PsiFile psiFile = getPsi(language); |
| return psiFile != null ? findElementAt(psiFile, offset) : null; |
| } |
| |
| @Override |
| @Nullable |
| public PsiReference findReferenceAt(final int offset, @NotNull final Language language) { |
| final PsiFile psiFile = getPsi(language); |
| return psiFile != null ? findReferenceAt(psiFile, offset) : null; |
| } |
| |
| @Nullable |
| private static PsiReference findReferenceAt(@Nullable final PsiFile psiFile, final int offset) { |
| if (psiFile == null) return null; |
| int offsetInElement = offset; |
| PsiElement child = psiFile.getFirstChild(); |
| while (child != null) { |
| final int length = child.getTextLength(); |
| if (length <= offsetInElement) { |
| offsetInElement -= length; |
| child = child.getNextSibling(); |
| continue; |
| } |
| return child.findReferenceAt(offsetInElement); |
| } |
| return null; |
| } |
| |
| @Override |
| public PsiElement findElementAt(final int offset) { |
| return findElementAt(getPsi(getBaseLanguage()), offset); |
| } |
| |
| |
| @Override |
| public PsiElement findElementAt(int offset, @NotNull Class<? extends Language> lang) { |
| if (!ReflectionUtil.isAssignable(lang, getBaseLanguage().getClass())) return null; |
| return findElementAt(offset); |
| } |
| |
| @Nullable |
| public static PsiElement findElementAt(@Nullable final PsiElement psiFile, final int offset) { |
| if (psiFile == null) return null; |
| int offsetInElement = offset; |
| PsiElement child = psiFile.getFirstChild(); |
| while (child != null) { |
| final int length = child.getTextLength(); |
| if (length <= offsetInElement) { |
| offsetInElement -= length; |
| child = child.getNextSibling(); |
| continue; |
| } |
| return child.findElementAt(offsetInElement); |
| } |
| return null; |
| } |
| |
| public void forceCachedPsi(@NotNull PsiFile psiFile) { |
| myPsiFile.set(psiFile); |
| ((PsiManagerEx)myManager).getFileManager().setViewProvider(getVirtualFile(), this); |
| } |
| |
| @NotNull |
| private Content getContent() { |
| return myContent; |
| } |
| |
| private void setContent(@NotNull Content content) { |
| // temporarily commented |
| //if (myPhysical) { |
| // final Content oldContent = myContent; |
| // if (oldContent != null && content.getModificationStamp() != oldContent.getModificationStamp()) { |
| // ApplicationManager.getApplication().assertWriteAccessAllowed(); |
| // } |
| //} |
| myContent = content; |
| } |
| |
| @NonNls |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + "{myVirtualFile=" + myVirtualFile + ", content=" + getContent() + '}'; |
| } |
| |
| private interface Content { |
| CharSequence getText(); |
| |
| long getModificationStamp(); |
| } |
| |
| private class VirtualFileContent implements Content { |
| @Override |
| public CharSequence getText() { |
| final VirtualFile virtualFile = getVirtualFile(); |
| if (virtualFile instanceof LightVirtualFile) { |
| Document doc = getCachedDocument(); |
| if (doc != null) return getLastCommittedText(doc); |
| return ((LightVirtualFile)virtualFile).getContent(); |
| } |
| |
| final Document document = getDocument(); |
| if (document == null) { |
| return LoadTextUtil.loadText(virtualFile); |
| } |
| else { |
| return getLastCommittedText(document); |
| } |
| } |
| |
| @Override |
| public long getModificationStamp() { |
| return getVirtualFile().getModificationStamp(); |
| } |
| |
| @NonNls |
| @Override |
| public String toString() { |
| return "VirtualFileContent{size=" + getVirtualFile().getLength() + "}"; |
| } |
| } |
| |
| private CharSequence getLastCommittedText(Document document) { |
| return PsiDocumentManager.getInstance(myManager.getProject()).getLastCommittedText(document); |
| } |
| |
| private class DocumentContent implements Content { |
| @NonNls |
| @Override |
| public String toString() { |
| final Document document = getDocument(); |
| return "DocumentContent{size=" + (document == null ? null : document.getTextLength()) + "}"; |
| } |
| |
| @NotNull |
| @Override |
| public CharSequence getText() { |
| final Document document = getDocument(); |
| assert document != null; |
| return getLastCommittedText(document); |
| } |
| |
| @Override |
| public long getModificationStamp() { |
| Document document = com.intellij.reference.SoftReference.dereference(myDocument); |
| if (document != null) return document.getModificationStamp(); |
| return myVirtualFile.getModificationStamp(); |
| } |
| } |
| |
| private class PsiFileContent implements Content { |
| private final PsiFileImpl myFile; |
| private volatile String myContent = null; |
| private final long myModificationStamp; |
| |
| @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") |
| private final List<FileElement> myFileElementHardRefs = new SmartList<FileElement>(); |
| |
| private PsiFileContent(final PsiFileImpl file, final long modificationStamp) { |
| myFile = file; |
| myModificationStamp = modificationStamp; |
| for (PsiFile aFile : getAllFiles()) { |
| if (aFile instanceof PsiFileImpl) { |
| myFileElementHardRefs.add(((PsiFileImpl)aFile).calcTreeElement()); |
| } |
| } |
| } |
| |
| @Override |
| public CharSequence getText() { |
| String content = myContent; |
| if (content == null) { |
| myContent = content = ApplicationManager.getApplication().runReadAction(new Computable<String>() { |
| @Override |
| public String compute() { |
| return myFile.calcTreeElement().getText(); |
| } |
| }); |
| } |
| return content; |
| } |
| |
| @Override |
| public long getModificationStamp() { |
| return myModificationStamp; |
| } |
| } |
| |
| @NotNull |
| @Override |
| public PsiFile getStubBindingRoot() { |
| final PsiFile psi = getPsi(getBaseLanguage()); |
| assert psi != null; |
| return psi; |
| } |
| } |