2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.psi.impl.file.impl;
19 import com.intellij.injected.editor.DocumentWindow;
20 import com.intellij.injected.editor.VirtualFileWindow;
21 import com.intellij.lang.Language;
22 import com.intellij.lang.LanguageUtil;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.Document;
26 import com.intellij.openapi.fileEditor.FileDocumentManager;
27 import com.intellij.openapi.fileTypes.FileType;
28 import com.intellij.openapi.project.DumbService;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.roots.FileIndexFacade;
31 import com.intellij.openapi.util.Disposer;
32 import com.intellij.openapi.util.Key;
33 import com.intellij.openapi.util.LowMemoryWatcher;
34 import com.intellij.openapi.util.registry.Registry;
35 import com.intellij.openapi.vfs.VfsUtilCore;
36 import com.intellij.openapi.vfs.VirtualFile;
37 import com.intellij.openapi.vfs.VirtualFileVisitor;
38 import com.intellij.psi.*;
39 import com.intellij.psi.impl.*;
40 import com.intellij.psi.impl.file.PsiDirectoryFactory;
41 import com.intellij.psi.impl.source.PsiFileImpl;
42 import com.intellij.testFramework.LightVirtualFile;
43 import com.intellij.util.ConcurrencyUtil;
44 import com.intellij.util.containers.ContainerUtil;
45 import com.intellij.util.messages.MessageBusConnection;
46 import gnu.trove.THashMap;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49 import org.jetbrains.annotations.TestOnly;
52 import java.util.concurrent.ConcurrentMap;
54 public class FileManagerImpl implements FileManager {
55 private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.file.impl.FileManagerImpl");
56 private final Key<FileViewProvider> myPsiHardRefKey = Key.create("HARD_REFERENCE_TO_PSI"); //non-static!
58 private final PsiManagerImpl myManager;
59 private final FileIndexFacade myFileIndex;
61 private final ConcurrentMap<VirtualFile, PsiDirectory> myVFileToPsiDirMap = ContainerUtil.createConcurrentSoftValueMap();
62 private final ConcurrentMap<VirtualFile, FileViewProvider> myVFileToViewProviderMap = ContainerUtil.createConcurrentWeakValueMap();
64 private boolean myInitialized = false;
65 private boolean myDisposed = false;
67 private final FileDocumentManager myFileDocumentManager;
68 private final MessageBusConnection myConnection;
70 public FileManagerImpl(PsiManagerImpl manager, FileDocumentManager fileDocumentManager, FileIndexFacade fileIndex) {
72 myFileIndex = fileIndex;
73 myConnection = manager.getProject().getMessageBus().connect();
75 myFileDocumentManager = fileDocumentManager;
77 Disposer.register(manager.getProject(), this);
78 LowMemoryWatcher.register(new Runnable() {
86 private static final VirtualFile NULL = new LightVirtualFile();
88 public void processQueue() {
89 // just to call processQueue()
90 myVFileToViewProviderMap.remove(NULL);
95 public ConcurrentMap<VirtualFile, FileViewProvider> getVFileToViewProviderMap() {
96 return myVFileToViewProviderMap;
99 private void updateAllViewProviders() {
100 handleFileTypesChange(new FileTypesChanged() {
102 protected void updateMaps() {
103 for (final FileViewProvider provider : myVFileToViewProviderMap.values()) {
104 if (!provider.getVirtualFile().isValid()) {
108 clearPsiCaches(provider);
110 removeInvalidFilesAndDirs(false);
111 checkLanguageChange();
116 public static void clearPsiCaches(FileViewProvider provider) {
117 if (provider instanceof SingleRootFileViewProvider) {
118 for (PsiFile root : ((SingleRootFileViewProvider)provider).getCachedPsiFiles()) {
119 if (root instanceof PsiFileImpl) {
120 ((PsiFileImpl)root).clearCaches();
124 for (Language language : provider.getLanguages()) {
125 final PsiFile psi = provider.getPsi(language);
126 if (psi instanceof PsiFileImpl) {
127 ((PsiFileImpl)psi).clearCaches();
133 private void checkLanguageChange() {
134 Map<VirtualFile, FileViewProvider> fileToPsiFileMap = new THashMap<VirtualFile, FileViewProvider>(myVFileToViewProviderMap);
135 Map<VirtualFile, FileViewProvider> originalFileToPsiFileMap = new THashMap<VirtualFile, FileViewProvider>(myVFileToViewProviderMap);
136 myVFileToViewProviderMap.clear();
137 for (Iterator<VirtualFile> iterator = fileToPsiFileMap.keySet().iterator(); iterator.hasNext();) {
138 VirtualFile vFile = iterator.next();
139 Language language = LanguageUtil.getLanguageForPsi(myManager.getProject(), vFile);
140 if (language != null && language != fileToPsiFileMap.get(vFile).getBaseLanguage()) {
144 myVFileToViewProviderMap.putAll(fileToPsiFileMap);
145 markInvalidations(originalFileToPsiFileMap);
148 public void forceReload(@NotNull VirtualFile vFile) {
149 LanguageSubstitutors.cancelReparsing(vFile);
150 if (findCachedViewProvider(vFile) == null) {
153 setViewProvider(vFile, null);
155 VirtualFile dir = vFile.getParent();
156 PsiDirectory parentDir = dir == null ? null : getCachedDirectory(dir);
157 PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
158 if (parentDir != null) {
159 event.setParent(parentDir);
160 myManager.childrenChanged(event);
163 firePropertyChangedForUnloadedPsi(event, vFile);
167 void firePropertyChangedForUnloadedPsi(@NotNull PsiTreeChangeEventImpl event, @NotNull VirtualFile vFile) {
168 event.setPropertyName(PsiTreeChangeEvent.PROP_UNLOADED_PSI);
169 event.setOldValue(vFile);
170 event.setNewValue(vFile);
172 myManager.beforePropertyChange(event);
173 myManager.propertyChanged(event);
177 public void dispose() {
179 myConnection.disconnect();
181 ApplicationManager.getApplication().assertWriteAccessAllowed();
182 clearViewProviders();
187 private void clearViewProviders() {
188 DebugUtil.startPsiModification("clearViewProviders");
190 for (final FileViewProvider provider : myVFileToViewProviderMap.values()) {
191 markInvalidated(provider);
193 myVFileToViewProviderMap.clear();
196 DebugUtil.finishPsiModification();
202 public void cleanupForNextTest() {
203 clearViewProviders();
204 myVFileToPsiDirMap.clear();
205 ((PsiModificationTrackerImpl)myManager.getModificationTracker()).incCounter();
210 public FileViewProvider findViewProvider(@NotNull final VirtualFile file) {
211 assert !file.isDirectory();
212 FileViewProvider viewProvider = findCachedViewProvider(file);
213 if (viewProvider != null) return viewProvider;
214 viewProvider = ConcurrencyUtil.cacheOrGet(myVFileToViewProviderMap, file, createFileViewProvider(file, true));
219 public FileViewProvider findCachedViewProvider(@NotNull final VirtualFile file) {
220 FileViewProvider viewProvider = getFromInjected(file);
221 if (viewProvider == null) viewProvider = myVFileToViewProviderMap.get(file);
222 if (viewProvider == null) viewProvider = file.getUserData(myPsiHardRefKey);
227 private FileViewProvider getFromInjected(@NotNull VirtualFile file) {
228 if (file instanceof VirtualFileWindow) {
229 DocumentWindow document = ((VirtualFileWindow)file).getDocumentWindow();
230 PsiFile psiFile = PsiDocumentManager.getInstance(myManager.getProject()).getCachedPsiFile(document);
231 if (psiFile == null) return null;
232 return psiFile.getViewProvider();
238 public void setViewProvider(@NotNull final VirtualFile virtualFile, @Nullable final FileViewProvider fileViewProvider) {
239 FileViewProvider prev = findCachedViewProvider(virtualFile);
240 if (prev == fileViewProvider) return;
242 DebugUtil.startPsiModification(null);
244 markInvalidated(prev);
245 DebugUtil.onInvalidated(prev);
248 DebugUtil.finishPsiModification();
252 if (!(virtualFile instanceof VirtualFileWindow)) {
253 if (fileViewProvider == null) {
254 myVFileToViewProviderMap.remove(virtualFile);
257 if (virtualFile instanceof LightVirtualFile) {
258 virtualFile.putUserData(myPsiHardRefKey, fileViewProvider);
260 myVFileToViewProviderMap.put(virtualFile, fileViewProvider);
268 public FileViewProvider createFileViewProvider(@NotNull final VirtualFile file, boolean eventSystemEnabled) {
269 FileType fileType = file.getFileType();
270 Language language = LanguageUtil.getLanguageForPsi(myManager.getProject(), file);
271 FileViewProviderFactory factory = language == null
272 ? FileTypeFileViewProviders.INSTANCE.forFileType(fileType)
273 : LanguageFileViewProviders.INSTANCE.forLanguage(language);
274 FileViewProvider viewProvider = factory == null ? null : factory.createFileViewProvider(file, language, myManager, eventSystemEnabled);
276 return viewProvider == null ? new SingleRootFileViewProvider(myManager, file, eventSystemEnabled, fileType) : viewProvider;
279 public void markInitialized() {
280 LOG.assertTrue(!myInitialized);
282 myInitialized = true;
284 myConnection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
286 public void enteredDumbMode() {
287 updateAllViewProviders();
291 public void exitDumbMode() {
292 updateAllViewProviders();
297 public boolean isInitialized() {
298 return myInitialized;
301 void processFileTypesChanged() {
302 handleFileTypesChange(new FileTypesChanged() {
304 protected void updateMaps() {
305 removeInvalidFilesAndDirs(true);
310 private abstract class FileTypesChanged implements Runnable {
311 protected abstract void updateMaps();
315 PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
316 event.setPropertyName(PsiTreeChangeEvent.PROP_FILE_TYPES);
317 myManager.beforePropertyChange(event);
321 myManager.propertyChanged(event);
325 private boolean myProcessingFileTypesChange = false;
326 private void handleFileTypesChange(@NotNull FileTypesChanged runnable) {
327 if (myProcessingFileTypesChange) return;
328 myProcessingFileTypesChange = true;
330 ApplicationManager.getApplication().runWriteAction(runnable);
333 myProcessingFileTypesChange = false;
337 void dispatchPendingEvents() {
338 if (!myInitialized) {
339 LOG.error("Project is not yet initialized: "+myManager.getProject());
342 LOG.error("Project is already disposed: "+myManager.getProject());
345 myConnection.deliverImmediately();
349 public void checkConsistency() {
350 HashMap<VirtualFile, FileViewProvider> fileToViewProvider = new HashMap<VirtualFile, FileViewProvider>(myVFileToViewProviderMap);
351 myVFileToViewProviderMap.clear();
352 for (VirtualFile vFile : fileToViewProvider.keySet()) {
353 final FileViewProvider fileViewProvider = fileToViewProvider.get(vFile);
355 LOG.assertTrue(vFile.isValid());
356 PsiFile psiFile1 = findFile(vFile);
357 if (psiFile1 != null && fileViewProvider != null && fileViewProvider.isPhysical()) { // might get collected
358 PsiFile psi = fileViewProvider.getPsi(fileViewProvider.getBaseLanguage());
359 assert psi != null : fileViewProvider +"; "+fileViewProvider.getBaseLanguage()+"; "+psiFile1;
360 assert psiFile1.getClass().equals(psi.getClass()) : psiFile1 +"; "+psi + "; "+psiFile1.getClass() +"; "+psi.getClass();
364 HashMap<VirtualFile, PsiDirectory> fileToPsiDirMap = new HashMap<VirtualFile, PsiDirectory>(myVFileToPsiDirMap);
365 myVFileToPsiDirMap.clear();
367 for (VirtualFile vFile : fileToPsiDirMap.keySet()) {
368 LOG.assertTrue(vFile.isValid());
369 PsiDirectory psiDir1 = findDirectory(vFile);
370 LOG.assertTrue(psiDir1 != null);
372 VirtualFile parent = vFile.getParent();
373 if (parent != null) {
374 LOG.assertTrue(myVFileToPsiDirMap.containsKey(parent));
381 public PsiFile findFile(@NotNull VirtualFile vFile) {
382 if (vFile.isDirectory()) return null;
383 final Project project = myManager.getProject();
384 if (project.isDefault()) return null;
386 ApplicationManager.getApplication().assertReadAccessAllowed();
387 if (!vFile.isValid()) {
388 LOG.error("Invalid file: " + vFile);
392 dispatchPendingEvents();
393 final FileViewProvider viewProvider = findViewProvider(vFile);
394 return viewProvider.getPsi(viewProvider.getBaseLanguage());
399 public PsiFile getCachedPsiFile(@NotNull VirtualFile vFile) {
400 ApplicationManager.getApplication().assertReadAccessAllowed();
401 LOG.assertTrue(vFile.isValid(), "Invalid file");
403 LOG.error("Project is already disposed: " + myManager.getProject());
405 if (!myInitialized) return null;
407 dispatchPendingEvents();
409 return getCachedPsiFileInner(vFile);
414 public PsiDirectory findDirectory(@NotNull VirtualFile vFile) {
415 LOG.assertTrue(myInitialized, "Access to psi files should be performed only after startup activity");
417 LOG.error("Access to psi files should not be performed after project disposal: "+myManager.getProject());
421 ApplicationManager.getApplication().assertReadAccessAllowed();
422 if (!vFile.isValid()) {
423 LOG.error("File is not valid:" + vFile);
427 if (!vFile.isDirectory()) return null;
428 dispatchPendingEvents();
430 return findDirectoryImpl(vFile);
434 private PsiDirectory findDirectoryImpl(@NotNull VirtualFile vFile) {
435 PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile);
436 if (psiDir != null) return psiDir;
438 if (Registry.is("ide.hide.excluded.files")) {
439 if (myFileIndex.isExcludedFile(vFile)) return null;
442 if (myFileIndex.isUnderIgnored(vFile)) return null;
445 VirtualFile parent = vFile.getParent();
446 if (parent != null) { //?
447 findDirectoryImpl(parent);// need to cache parent directory - used for firing events
450 psiDir = PsiDirectoryFactory.getInstance(myManager.getProject()).createDirectory(vFile);
451 return ConcurrencyUtil.cacheOrGet(myVFileToPsiDirMap, vFile, psiDir);
454 public PsiDirectory getCachedDirectory(@NotNull VirtualFile vFile) {
455 return myVFileToPsiDirMap.get(vFile);
458 void removeFilesAndDirsRecursively(@NotNull VirtualFile vFile) {
459 DebugUtil.startPsiModification("removeFilesAndDirsRecursively");
461 VfsUtilCore.visitChildrenRecursively(vFile, new VirtualFileVisitor() {
463 public boolean visitFile(@NotNull VirtualFile file) {
464 if (file.isDirectory()) {
465 myVFileToPsiDirMap.remove(file);
468 FileViewProvider viewProvider = myVFileToViewProviderMap.remove(file);
469 if (viewProvider != null) {
470 markInvalidated(viewProvider);
478 DebugUtil.finishPsiModification();
482 private void markInvalidated(@NotNull FileViewProvider viewProvider) {
483 if (viewProvider instanceof SingleRootFileViewProvider) {
484 ((SingleRootFileViewProvider)viewProvider).markInvalidated();
486 VirtualFile virtualFile = viewProvider.getVirtualFile();
487 Document document = FileDocumentManager.getInstance().getCachedDocument(virtualFile);
488 if (document != null) {
489 ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(myManager.getProject())).associatePsi(document, null);
491 virtualFile.putUserData(myPsiHardRefKey, null);
495 PsiFile getCachedPsiFileInner(@NotNull VirtualFile file) {
496 FileViewProvider fileViewProvider = myVFileToViewProviderMap.get(file);
497 if (fileViewProvider == null) fileViewProvider = file.getUserData(myPsiHardRefKey);
498 return fileViewProvider instanceof SingleRootFileViewProvider
499 ? ((SingleRootFileViewProvider)fileViewProvider).getCachedPsi(fileViewProvider.getBaseLanguage()) : null;
504 public List<PsiFile> getAllCachedFiles() {
505 List<PsiFile> files = new ArrayList<PsiFile>();
506 for (FileViewProvider provider : myVFileToViewProviderMap.values()) {
507 if (provider instanceof SingleRootFileViewProvider) {
508 ContainerUtil.addIfNotNull(files, ((SingleRootFileViewProvider)provider).getCachedPsi(provider.getBaseLanguage()));
514 void removeInvalidFilesAndDirs(boolean useFind) {
515 Map<VirtualFile, PsiDirectory> fileToPsiDirMap = new THashMap<VirtualFile, PsiDirectory>(myVFileToPsiDirMap);
517 myVFileToPsiDirMap.clear();
519 for (Iterator<VirtualFile> iterator = fileToPsiDirMap.keySet().iterator(); iterator.hasNext();) {
520 VirtualFile vFile = iterator.next();
521 if (!vFile.isValid()) {
525 PsiDirectory psiDir = findDirectory(vFile);
526 if (psiDir == null) {
531 myVFileToPsiDirMap.clear();
532 myVFileToPsiDirMap.putAll(fileToPsiDirMap);
534 // note: important to update directories map first - findFile uses findDirectory!
535 Map<VirtualFile, FileViewProvider> fileToPsiFileMap = new THashMap<VirtualFile, FileViewProvider>(myVFileToViewProviderMap);
536 Map<VirtualFile, FileViewProvider> originalFileToPsiFileMap = new THashMap<VirtualFile, FileViewProvider>(myVFileToViewProviderMap);
538 myVFileToViewProviderMap.clear();
540 for (Iterator<VirtualFile> iterator = fileToPsiFileMap.keySet().iterator(); iterator.hasNext();) {
541 VirtualFile vFile = iterator.next();
543 if (!vFile.isValid()) {
549 FileViewProvider view = fileToPsiFileMap.get(vFile);
550 if (view == null) { // soft ref. collected
554 PsiFile psiFile1 = findFile(vFile);
555 if (psiFile1 == null) {
560 if (!areViewProvidersEquivalent(view, psiFile1.getViewProvider())) {
564 clearPsiCaches(view);
568 myVFileToViewProviderMap.clear();
569 myVFileToViewProviderMap.putAll(fileToPsiFileMap);
571 markInvalidations(originalFileToPsiFileMap);
574 static boolean areViewProvidersEquivalent(@NotNull FileViewProvider view1, @NotNull FileViewProvider view2) {
575 if (view1.getClass() != view2.getClass() || view1.getFileType() != view2.getFileType()) return false;
577 Language baseLanguage = view1.getBaseLanguage();
578 if (baseLanguage != view2.getBaseLanguage()) return false;
580 if (!view1.getLanguages().equals(view2.getLanguages())) return false;
581 PsiFile psi1 = view1.getPsi(baseLanguage);
582 PsiFile psi2 = view2.getPsi(baseLanguage);
583 if (psi1 == null) return psi2 == null;
584 if (psi1.getClass() != psi2.getClass()) return false;
589 private void markInvalidations(Map<VirtualFile, FileViewProvider> originalFileToPsiFileMap) {
590 DebugUtil.startPsiModification(null);
592 for (Map.Entry<VirtualFile, FileViewProvider> entry : originalFileToPsiFileMap.entrySet()) {
593 FileViewProvider viewProvider = entry.getValue();
594 if (myVFileToViewProviderMap.get(entry.getKey()) != viewProvider) {
595 markInvalidated(viewProvider);
600 DebugUtil.finishPsiModification();
605 public void reloadFromDisk(@NotNull PsiFile file) {
606 reloadFromDisk(file, false);
609 void reloadFromDisk(@NotNull PsiFile file, boolean ignoreDocument) {
610 ApplicationManager.getApplication().assertWriteAccessAllowed();
611 VirtualFile vFile = file.getVirtualFile();
612 assert vFile != null;
614 if (file instanceof PsiBinaryFile) return;
615 FileDocumentManager fileDocumentManager = myFileDocumentManager;
616 Document document = fileDocumentManager.getCachedDocument(vFile);
617 if (document != null && !ignoreDocument){
618 fileDocumentManager.reloadFromDisk(document);
621 FileViewProvider latestProvider = createFileViewProvider(vFile, false);
622 if (latestProvider.getPsi(latestProvider.getBaseLanguage()) instanceof PsiBinaryFile) {
627 FileViewProvider viewProvider = file.getViewProvider();
628 if (viewProvider instanceof SingleRootFileViewProvider) {
629 ((SingleRootFileViewProvider)viewProvider).onContentReload();
631 LOG.error("Invalid view provider: " + viewProvider + " of " + viewProvider.getClass());