Merge branch 'appcode10' into merge_appcode10
[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(), "Invalid file");
313     if (myDisposed) {
314       LOG.error("Project is already disposed: " + myManager.getProject());
315     }
316     if (!myInitialized) return null;
317
318     dispatchPendingEvents();
319
320     return getCachedPsiFileInner(vFile);
321   }
322
323   @Nullable
324   public PsiDirectory findDirectory(@NotNull VirtualFile vFile) {
325     LOG.assertTrue(myInitialized, "Access to psi files should be performed only after startup activity");
326     LOG.assertTrue(!myDisposed, "Access to psi files should not be performed after disposal");
327
328     ApplicationManager.getApplication().assertReadAccessAllowed();
329     if (!vFile.isValid()) {
330       LOG.error("File is not valid:" + vFile.getName());
331     }
332
333     if (!vFile.isDirectory()) return null;
334     dispatchPendingEvents();
335
336     return findDirectoryImpl(vFile);
337   }
338
339   @Nullable
340   private PsiDirectory findDirectoryImpl(final VirtualFile vFile) {
341     PsiDirectory psiDir = myVFileToPsiDirMap.get(vFile);
342     if (psiDir != null) return psiDir;
343
344     if (myFileIndex.isExcludedFile(vFile)) return null;
345
346     VirtualFile parent = vFile.getParent();
347     if (parent != null) { //?
348       findDirectoryImpl(parent);// need to cache parent directory - used for firing events
349     }
350
351     psiDir = PsiDirectoryFactory.getInstance(myManager.getProject()).createDirectory(vFile);
352     return ConcurrencyUtil.cacheOrGet(myVFileToPsiDirMap, vFile, psiDir);
353   }
354
355   PsiDirectory getCachedDirectory(VirtualFile vFile) {
356     return myVFileToPsiDirMap.get(vFile);
357   }
358
359   void cacheViewProvider(VirtualFile vFile, FileViewProvider viewProvider) {
360     myVFileToViewProviderMap.put(vFile, viewProvider);
361   }
362
363   void removeCachedViewProvider(VirtualFile vFile) {
364     myVFileToViewProviderMap.remove(vFile);
365   }
366
367   void removeFilesAndDirsRecursively(VirtualFile vFile) {
368     if (vFile.isDirectory()) {
369       myVFileToPsiDirMap.remove(vFile);
370
371       VirtualFile[] children = vFile.getChildren();
372       for (VirtualFile child : children) {
373         removeFilesAndDirsRecursively(child);
374       }
375     }
376     else {
377       myVFileToViewProviderMap.remove(vFile);
378     }
379   }
380
381   @Nullable
382   PsiFile getCachedPsiFileInner(VirtualFile file) {
383     final FileViewProvider fileViewProvider = myVFileToViewProviderMap.get(file);
384     return fileViewProvider instanceof SingleRootFileViewProvider
385            ? ((SingleRootFileViewProvider)fileViewProvider).getCachedPsi(fileViewProvider.getBaseLanguage()) : null;
386   }
387
388   public List<PsiFile> getAllCachedFiles() {
389     List<PsiFile> files = new ArrayList<PsiFile>();
390     for (FileViewProvider provider : myVFileToViewProviderMap.values()) {
391       if (provider instanceof SingleRootFileViewProvider) {
392         files.add(((SingleRootFileViewProvider)provider).getCachedPsi(provider.getBaseLanguage()));
393       }
394     }
395     return files;
396   }
397
398   void removeInvalidFilesAndDirs(boolean useFind) {
399     Map<VirtualFile, PsiDirectory> fileToPsiDirMap = new THashMap<VirtualFile, PsiDirectory>(myVFileToPsiDirMap);
400     if (useFind) {
401       myVFileToPsiDirMap.clear();
402     }
403     for (Iterator<VirtualFile> iterator = fileToPsiDirMap.keySet().iterator(); iterator.hasNext();) {
404       VirtualFile vFile = iterator.next();
405       if (!vFile.isValid()) {
406         iterator.remove();
407       }
408       else {
409         PsiDirectory psiDir = findDirectory(vFile);
410         if (psiDir == null) {
411           iterator.remove();
412         }
413       }
414     }
415     myVFileToPsiDirMap.clear();
416     myVFileToPsiDirMap.putAll(fileToPsiDirMap);
417
418     // note: important to update directories map first - findFile uses findDirectory!
419     Map<VirtualFile, FileViewProvider> fileToPsiFileMap = new THashMap<VirtualFile, FileViewProvider>(myVFileToViewProviderMap);
420     if (useFind) {
421       myVFileToViewProviderMap.clear();
422     }
423     for (Iterator<VirtualFile> iterator = fileToPsiFileMap.keySet().iterator(); iterator.hasNext();) {
424       VirtualFile vFile = iterator.next();
425
426       if (!vFile.isValid()) {
427         iterator.remove();
428         continue;
429       }
430
431       if (useFind) {
432         FileViewProvider view = fileToPsiFileMap.get(vFile);
433         if (view == null) { // soft ref. collected
434           iterator.remove();
435           continue;
436         }
437         PsiFile psiFile1 = findFile(vFile);
438         if (psiFile1 == null) {
439           iterator.remove();
440           continue;
441         }
442
443         PsiFile psi = view.getPsi(view.getBaseLanguage());
444         if (psi == null || !psiFile1.getClass().equals(psi.getClass()) ||
445              psiFile1.getViewProvider().getBaseLanguage() != view.getBaseLanguage() // e.g. JSP <-> JSPX
446            ) {
447           iterator.remove();
448         }
449         else if (psi instanceof PsiFileImpl) {
450           ((PsiFileImpl)psi).clearCaches();
451         }
452       }
453     }
454     myVFileToViewProviderMap.clear();
455     myVFileToViewProviderMap.putAll(fileToPsiFileMap);
456   }
457
458   public void reloadFromDisk(@NotNull PsiFile file) {
459     reloadFromDisk(file, false);
460   }
461
462   void reloadFromDisk(PsiFile file, boolean ignoreDocument) {
463     VirtualFile vFile = file.getVirtualFile();
464     assert vFile != null;
465
466     if (file instanceof PsiBinaryFile) return;
467     FileDocumentManager fileDocumentManager = myFileDocumentManager;
468     Document document = fileDocumentManager.getCachedDocument(vFile);
469     if (document != null && !ignoreDocument){
470       fileDocumentManager.reloadFromDisk(document);
471     }
472     else{
473       PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
474       event.setParent(file);
475       event.setFile(file);
476       if (file instanceof PsiFileImpl && ((PsiFileImpl)file).isContentsLoaded()) {
477         event.setOffset(0);
478         event.setOldLength(file.getTextLength());
479       }
480       myManager.beforeChildrenChange(event);
481
482       if (file instanceof PsiFileEx) {
483         ((PsiFileEx)file).onContentReload();
484       }
485
486       myManager.childrenChanged(event);
487     }
488   }
489
490   @SuppressWarnings({"HardCodedStringLiteral"})
491   public void dumpFilesWithContentLoaded(Writer out) throws IOException {
492     out.write("Files with content loaded cached in FileManagerImpl:\n");
493     Set<VirtualFile> vFiles = myVFileToViewProviderMap.keySet();
494     for (VirtualFile fileCacheEntry : vFiles) {
495       final FileViewProvider view = myVFileToViewProviderMap.get(fileCacheEntry);
496       PsiFile psiFile = view.getPsi(view.getBaseLanguage());
497       if (psiFile instanceof PsiFileImpl && ((PsiFileImpl)psiFile).isContentsLoaded()) {
498         out.write(fileCacheEntry.getPresentableUrl());
499         out.write("\n");
500       }
501     }
502   }
503 }