ExcludedFileIndex -> FileIndexFacade; use it in PsiClassImplUtil
[idea/community.git] / platform / core-impl / src / com / intellij / psi / impl / file / impl / FileManagerImpl.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.psi.impl.file.impl;
18
19 import com.intellij.injected.editor.DocumentWindow;
20 import com.intellij.injected.editor.VirtualFileWindow;
21 import com.intellij.lang.Language;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.editor.Document;
25 import com.intellij.openapi.extensions.Extensions;
26 import com.intellij.openapi.fileEditor.FileDocumentManager;
27 import com.intellij.openapi.fileTypes.ContentBasedFileSubstitutor;
28 import com.intellij.openapi.fileTypes.FileType;
29 import com.intellij.openapi.fileTypes.LanguageFileType;
30 import com.intellij.openapi.project.DumbService;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.roots.FileIndexFacade;
33 import com.intellij.openapi.util.Disposer;
34 import com.intellij.openapi.vfs.VirtualFile;
35 import com.intellij.psi.*;
36 import com.intellij.psi.impl.PsiFileEx;
37 import com.intellij.psi.impl.PsiManagerImpl;
38 import com.intellij.psi.impl.PsiTreeChangeEventImpl;
39 import com.intellij.psi.impl.file.PsiDirectoryFactory;
40 import com.intellij.psi.impl.source.PsiFileImpl;
41 import com.intellij.util.ConcurrencyUtil;
42 import com.intellij.util.containers.ConcurrentSoftValueHashMap;
43 import com.intellij.util.containers.ConcurrentWeakValueHashMap;
44 import com.intellij.util.messages.MessageBusConnection;
45 import gnu.trove.THashMap;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
48 import org.jetbrains.annotations.TestOnly;
49
50 import java.io.IOException;
51 import java.io.Writer;
52 import java.util.*;
53 import java.util.concurrent.ConcurrentMap;
54
55 public class FileManagerImpl implements FileManager {
56   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.file.impl.FileManagerImpl");
57
58   private final PsiManagerImpl myManager;
59   private final FileIndexFacade myFileIndex;
60
61   private final ConcurrentMap<VirtualFile, PsiDirectory> myVFileToPsiDirMap = new ConcurrentSoftValueHashMap<VirtualFile, PsiDirectory>();
62   private final ConcurrentWeakValueHashMap<VirtualFile, FileViewProvider> myVFileToViewProviderMap = new ConcurrentWeakValueHashMap<VirtualFile, FileViewProvider>();
63
64   private boolean myInitialized = false;
65   private boolean myDisposed = false;
66
67   private final FileDocumentManager myFileDocumentManager;
68   private final MessageBusConnection myConnection;
69
70   public FileManagerImpl(PsiManagerImpl manager,
71                          FileDocumentManager fileDocumentManager,
72                          FileIndexFacade fileIndex) {
73     myManager = manager;
74     myFileIndex = fileIndex;
75     myConnection = manager.getProject().getMessageBus().connect();
76
77     myFileDocumentManager = fileDocumentManager;
78
79     myConnection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
80
81       public void enteredDumbMode() {
82         recalcAllViewProviders();
83       }
84
85       public void exitDumbMode() {
86         recalcAllViewProviders();
87       }
88     });
89     Disposer.register(manager.getProject(), this);
90   }
91
92   public void processQueue() {
93     myVFileToViewProviderMap.processQueue();
94   }
95
96   @TestOnly
97   public ConcurrentWeakValueHashMap<VirtualFile, FileViewProvider> getVFileToViewProviderMap() {
98     return myVFileToViewProviderMap;
99   }
100
101   private void recalcAllViewProviders() {
102     handleFileTypesChange(new FileTypesChanged() {
103       protected void updateMaps() {
104         for (final FileViewProvider provider : myVFileToViewProviderMap.values()) {
105           if (!provider.getVirtualFile().isValid()) {
106             continue;
107           }
108
109           for (Language language : provider.getLanguages()) {
110             final PsiFile psi = provider.getPsi(language);
111             if (psi instanceof PsiFileImpl) {
112               ((PsiFileImpl)psi).clearCaches();
113             }
114           }
115         }
116         removeInvalidFilesAndDirs(false);
117       }
118     });
119   }
120
121   public void dispose() {
122     if (myInitialized) {
123       myConnection.disconnect();
124     }
125     myDisposed = true;
126   }
127
128   @TestOnly
129   public void cleanupForNextTest() {
130     myVFileToViewProviderMap.clear();
131     myVFileToPsiDirMap.clear();
132     processQueue();
133   }
134
135   @NotNull
136   public FileViewProvider findViewProvider(@NotNull final VirtualFile file) {
137     FileViewProvider viewProvider = getFromInjected(file);
138     if (viewProvider != null) return viewProvider;
139     viewProvider = myVFileToViewProviderMap.get(file);
140     if(viewProvider == null) {
141       viewProvider = ConcurrencyUtil.cacheOrGet(myVFileToViewProviderMap, file, createFileViewProvider(file, true));
142     }
143     return viewProvider;
144   }
145
146   public FileViewProvider findCachedViewProvider(@NotNull final VirtualFile file) {
147     FileViewProvider viewProvider = getFromInjected(file);
148     if (viewProvider != null) return viewProvider;
149     return myVFileToViewProviderMap.get(file);
150   }
151
152   @Nullable
153   private FileViewProvider getFromInjected(VirtualFile file) {
154     if (file instanceof VirtualFileWindow) {
155       DocumentWindow document = ((VirtualFileWindow)file).getDocumentWindow();
156       PsiFile psiFile = PsiDocumentManager.getInstance(myManager.getProject()).getCachedPsiFile(document);
157       if (psiFile == null) return null;
158       return psiFile.getViewProvider();
159     }
160     return null;
161   }
162
163   public void setViewProvider(@NotNull final VirtualFile virtualFile, final FileViewProvider fileViewProvider) {
164     if (!(virtualFile instanceof VirtualFileWindow)) {
165       if (fileViewProvider == null) {
166         myVFileToViewProviderMap.remove(virtualFile);
167       }
168       else {
169         myVFileToViewProviderMap.put(virtualFile, fileViewProvider);
170       }
171     }
172   }
173
174   @NotNull
175   public FileViewProvider createFileViewProvider(@NotNull final VirtualFile file, boolean physical) {
176     Language language = getLanguage(file);
177     final FileViewProviderFactory factory = language == null
178                                             ? FileTypeFileViewProviders.INSTANCE.forFileType(file.getFileType())
179                                             : LanguageFileViewProviders.INSTANCE.forLanguage(language);
180     FileViewProvider viewProvider = factory == null ? null : factory.createFileViewProvider(file, language, myManager, physical);
181
182     return viewProvider == null ? new SingleRootFileViewProvider(myManager, file, physical) : viewProvider;
183   }
184
185   @Nullable
186   private Language getLanguage(final VirtualFile file) {
187     final FileType fileType = file.getFileType();
188     Project project = myManager.getProject();
189     if (fileType instanceof LanguageFileType) {
190       return LanguageSubstitutors.INSTANCE.substituteLanguage(((LanguageFileType)fileType).getLanguage(), file, project);
191     }
192     // Define language for binary file
193     final ContentBasedFileSubstitutor[] processors = Extensions.getExtensions(ContentBasedFileSubstitutor.EP_NAME);
194     for (ContentBasedFileSubstitutor processor : processors) {
195       Language language = processor.obtainLanguageForFile(file);
196       if (language != null) {
197         return language;
198       }
199     }
200
201     return null;
202   }
203
204   public void markInitialized() {
205     LOG.assertTrue(!myInitialized);
206     myDisposed = false;
207     myInitialized = true;
208   }
209
210   public boolean isInitialized() {
211     return myInitialized;
212   }
213
214   void processFileTypesChanged() {
215     handleFileTypesChange(new FileTypesChanged() {
216       protected void updateMaps() {
217         removeInvalidFilesAndDirs(true);
218       }
219     });
220   }
221
222   private abstract class FileTypesChanged implements Runnable {
223     protected abstract void updateMaps();
224
225     public void run() {
226       PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
227       event.setPropertyName(PsiTreeChangeEvent.PROP_FILE_TYPES);
228       myManager.beforePropertyChange(event);
229
230       updateMaps();
231
232       event = new PsiTreeChangeEventImpl(myManager);
233       event.setPropertyName(PsiTreeChangeEvent.PROP_FILE_TYPES);
234       myManager.propertyChanged(event);
235     }
236   }
237
238   private boolean myProcessingFileTypesChange = false;
239   private void handleFileTypesChange(final FileTypesChanged runnable) {
240     if (myProcessingFileTypesChange) return;
241     myProcessingFileTypesChange = true;
242     try {
243       ApplicationManager.getApplication().runWriteAction(runnable);
244     }
245     finally {
246       myProcessingFileTypesChange = false;
247     }
248   }
249
250   void dispatchPendingEvents() {
251     if (!myInitialized) {
252       LOG.error("Project is not yet initialized: "+myManager.getProject());
253     }
254     if (myDisposed) {
255       LOG.error("Project is already disposed: "+myManager.getProject());
256     }
257
258     myConnection.deliverImmediately();
259   }
260
261   @TestOnly
262   public void checkConsistency() {
263     HashMap<VirtualFile, FileViewProvider> fileToViewProvider = new HashMap<VirtualFile, FileViewProvider>(myVFileToViewProviderMap);
264     myVFileToViewProviderMap.clear();
265     for (VirtualFile vFile : fileToViewProvider.keySet()) {
266       final FileViewProvider fileViewProvider = fileToViewProvider.get(vFile);
267
268       LOG.assertTrue(vFile.isValid());
269       PsiFile psiFile1 = findFile(vFile);
270       if (psiFile1 != null && fileViewProvider != null && fileViewProvider.isPhysical()) { // might get collected
271         PsiFile psi = fileViewProvider.getPsi(fileViewProvider.getBaseLanguage());
272         assert psi != null : fileViewProvider +"; "+fileViewProvider.getBaseLanguage()+"; "+psiFile1;
273         assert psiFile1.getClass().equals(psi.getClass()) : psiFile1 +"; "+psi + "; "+psiFile1.getClass() +"; "+psi.getClass();
274       }
275     }
276
277     HashMap<VirtualFile, PsiDirectory> fileToPsiDirMap = new HashMap<VirtualFile, PsiDirectory>(myVFileToPsiDirMap);
278     myVFileToPsiDirMap.clear();
279
280     for (VirtualFile vFile : fileToPsiDirMap.keySet()) {
281       LOG.assertTrue(vFile.isValid());
282       PsiDirectory psiDir1 = findDirectory(vFile);
283       LOG.assertTrue(psiDir1 != null);
284
285       VirtualFile parent = vFile.getParent();
286       if (parent != null) {
287         LOG.assertTrue(myVFileToPsiDirMap.containsKey(parent));
288       }
289     }
290   }
291
292   @Nullable
293   public PsiFile findFile(@NotNull VirtualFile vFile) {
294     if (vFile.isDirectory()) return null;
295     final Project project = myManager.getProject();
296     if (project.isDefault()) return null;
297
298     ApplicationManager.getApplication().assertReadAccessAllowed();
299     if (!vFile.isValid()) {
300       LOG.error("Invalid file: " + vFile);
301       return null;
302     }
303
304     dispatchPendingEvents();
305     final FileViewProvider viewProvider = findViewProvider(vFile);
306     return viewProvider.getPsi(viewProvider.getBaseLanguage());
307   }
308
309   @Nullable
310   public PsiFile getCachedPsiFile(@NotNull VirtualFile vFile) {
311     ApplicationManager.getApplication().assertReadAccessAllowed();
312     LOG.assertTrue(vFile.isValid());
313     LOG.assertTrue(!myDisposed);
314     if (!myInitialized) return null;
315
316     dispatchPendingEvents();
317
318     return getCachedPsiFileInner(vFile);
319   }
320
321   @Nullable
322   public PsiDirectory findDirectory(@NotNull VirtualFile vFile) {
323     LOG.assertTrue(myInitialized, "Access to psi files should be performed only after startup activity");
324     LOG.assertTrue(!myDisposed, "Access to psi files should not be performed after disposal");
325
326     ApplicationManager.getApplication().assertReadAccessAllowed();
327     if (!vFile.isValid()) {
328       LOG.error("File is not valid:" + vFile.getName());
329     }
330
331     if (!vFile.isDirectory()) return null;
332     dispatchPendingEvents();
333
334     return findDirectoryImpl(vFile);
335   }
336
337   @Nullable
338   private PsiDirectory findDirectoryImpl(final VirtualFile vFile) {
339     PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile);
340     if (psiDir != null) return psiDir;
341
342     if (myFileIndex.isExcludedFile(vFile)) return null;
343
344     VirtualFile parent = vFile.getParent();
345     if (parent != null) { //?
346       findDirectoryImpl(parent);// need to cache parent directory - used for firing events
347     }
348
349     psiDir = PsiDirectoryFactory.getInstance(myManager.getProject()).createDirectory(vFile);
350     return ConcurrencyUtil.cacheOrGet(myVFileToPsiDirMap, vFile, psiDir);
351   }
352
353   PsiDirectory getCachedDirectory(VirtualFile vFile) {
354     return myVFileToPsiDirMap.get(vFile);
355   }
356
357   void cacheViewProvider(VirtualFile vFile, FileViewProvider viewProvider) {
358     myVFileToViewProviderMap.put(vFile, viewProvider);
359   }
360
361   void removeCachedViewProvider(VirtualFile vFile) {
362     myVFileToViewProviderMap.remove(vFile);
363   }
364
365   void removeFilesAndDirsRecursively(VirtualFile vFile) {
366     if (vFile.isDirectory()) {
367       myVFileToPsiDirMap.remove(vFile);
368
369       VirtualFile[] children = vFile.getChildren();
370       for (VirtualFile child : children) {
371         removeFilesAndDirsRecursively(child);
372       }
373     }
374     else {
375       myVFileToViewProviderMap.remove(vFile);
376     }
377   }
378
379   @Nullable
380   PsiFile getCachedPsiFileInner(VirtualFile file) {
381     final FileViewProvider fileViewProvider = myVFileToViewProviderMap.get(file);
382     return fileViewProvider instanceof SingleRootFileViewProvider
383            ? ((SingleRootFileViewProvider)fileViewProvider).getCachedPsi(fileViewProvider.getBaseLanguage()) : null;
384   }
385
386   public List<PsiFile> getAllCachedFiles() {
387     List<PsiFile> files = new ArrayList<PsiFile>();
388     for (FileViewProvider provider : myVFileToViewProviderMap.values()) {
389       if (provider instanceof SingleRootFileViewProvider) {
390         files.add(((SingleRootFileViewProvider)provider).getCachedPsi(provider.getBaseLanguage()));
391       }
392     }
393     return files;
394   }
395
396   void removeInvalidFilesAndDirs(boolean useFind) {
397     Map<VirtualFile, PsiDirectory> fileToPsiDirMap = new THashMap<VirtualFile, PsiDirectory>(myVFileToPsiDirMap);
398     if (useFind) {
399       myVFileToPsiDirMap.clear();
400     }
401     for (Iterator<VirtualFile> iterator = fileToPsiDirMap.keySet().iterator(); iterator.hasNext();) {
402       VirtualFile vFile = iterator.next();
403       if (!vFile.isValid()) {
404         iterator.remove();
405       }
406       else {
407         PsiDirectory psiDir = findDirectory(vFile);
408         if (psiDir == null) {
409           iterator.remove();
410         }
411       }
412     }
413     myVFileToPsiDirMap.clear();
414     myVFileToPsiDirMap.putAll(fileToPsiDirMap);
415
416     // note: important to update directories map first - findFile uses findDirectory!
417     Map<VirtualFile, FileViewProvider> fileToPsiFileMap = new THashMap<VirtualFile, FileViewProvider>(myVFileToViewProviderMap);
418     if (useFind) {
419       myVFileToViewProviderMap.clear();
420     }
421     for (Iterator<VirtualFile> iterator = fileToPsiFileMap.keySet().iterator(); iterator.hasNext();) {
422       VirtualFile vFile = iterator.next();
423
424       if (!vFile.isValid()) {
425         iterator.remove();
426         continue;
427       }
428
429       if (useFind) {
430         FileViewProvider view = fileToPsiFileMap.get(vFile);
431         if (view == null) { // soft ref. collected
432           iterator.remove();
433           continue;
434         }
435         PsiFile psiFile1 = findFile(vFile);
436         if (psiFile1 == null) {
437           iterator.remove();
438           continue;
439         }
440
441         PsiFile psi = view.getPsi(view.getBaseLanguage());
442         if (psi == null || !psiFile1.getClass().equals(psi.getClass()) ||
443              psiFile1.getViewProvider().getBaseLanguage() != view.getBaseLanguage() // e.g. JSP <-> JSPX
444            ) {
445           iterator.remove();
446         }
447         else if (psi instanceof PsiFileImpl) {
448           ((PsiFileImpl)psi).clearCaches();
449         }
450       }
451     }
452     myVFileToViewProviderMap.clear();
453     myVFileToViewProviderMap.putAll(fileToPsiFileMap);
454   }
455
456   public void reloadFromDisk(@NotNull PsiFile file) {
457     reloadFromDisk(file, false);
458   }
459
460   void reloadFromDisk(PsiFile file, boolean ignoreDocument) {
461     VirtualFile vFile = file.getVirtualFile();
462     assert vFile != null;
463
464     if (file instanceof PsiBinaryFile) return;
465     FileDocumentManager fileDocumentManager = myFileDocumentManager;
466     Document document = fileDocumentManager.getCachedDocument(vFile);
467     if (document != null && !ignoreDocument){
468       fileDocumentManager.reloadFromDisk(document);
469     }
470     else{
471       PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
472       event.setParent(file);
473       event.setFile(file);
474       if (file instanceof PsiFileImpl && ((PsiFileImpl)file).isContentsLoaded()) {
475         event.setOffset(0);
476         event.setOldLength(file.getTextLength());
477       }
478       myManager.beforeChildrenChange(event);
479
480       if (file instanceof PsiFileEx) {
481         ((PsiFileEx)file).onContentReload();
482       }
483
484       myManager.childrenChanged(event);
485     }
486   }
487
488   @SuppressWarnings({"HardCodedStringLiteral"})
489   public void dumpFilesWithContentLoaded(Writer out) throws IOException {
490     out.write("Files with content loaded cached in FileManagerImpl:\n");
491     Set<VirtualFile> vFiles = myVFileToViewProviderMap.keySet();
492     for (VirtualFile fileCacheEntry : vFiles) {
493       final FileViewProvider view = myVFileToViewProviderMap.get(fileCacheEntry);
494       PsiFile psiFile = view.getPsi(view.getBaseLanguage());
495       if (psiFile instanceof PsiFileImpl && ((PsiFileImpl)psiFile).isContentsLoaded()) {
496         out.write(fileCacheEntry.getPresentableUrl());
497         out.write("\n");
498       }
499     }
500   }
501 }