fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / core-impl / src / com / intellij / psi / impl / file / impl / FileManagerImpl.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.psi.impl.file.impl;
3
4 import com.google.common.annotations.VisibleForTesting;
5 import com.intellij.injected.editor.VirtualFileWindow;
6 import com.intellij.lang.Language;
7 import com.intellij.lang.LanguageUtil;
8 import com.intellij.openapi.application.ApplicationManager;
9 import com.intellij.openapi.diagnostic.Logger;
10 import com.intellij.openapi.editor.Document;
11 import com.intellij.openapi.fileEditor.FileDocumentManager;
12 import com.intellij.openapi.fileTypes.FileType;
13 import com.intellij.openapi.project.DumbService;
14 import com.intellij.openapi.roots.FileIndexFacade;
15 import com.intellij.openapi.util.*;
16 import com.intellij.openapi.util.registry.Registry;
17 import com.intellij.openapi.vfs.InvalidVirtualFileAccessException;
18 import com.intellij.openapi.vfs.VfsUtilCore;
19 import com.intellij.openapi.vfs.VirtualFile;
20 import com.intellij.openapi.vfs.VirtualFileVisitor;
21 import com.intellij.psi.*;
22 import com.intellij.psi.impl.*;
23 import com.intellij.psi.impl.file.PsiDirectoryFactory;
24 import com.intellij.testFramework.LightVirtualFile;
25 import com.intellij.util.ConcurrencyUtil;
26 import com.intellij.util.containers.ContainerUtil;
27 import com.intellij.util.messages.MessageBusConnection;
28 import gnu.trove.THashMap;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31 import org.jetbrains.annotations.TestOnly;
32
33 import java.util.*;
34 import java.util.concurrent.ConcurrentMap;
35 import java.util.concurrent.atomic.AtomicReference;
36
37 public final class FileManagerImpl implements FileManager {
38   private static final Key<Boolean> IN_COMA = Key.create("IN_COMA");
39   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.file.impl.FileManagerImpl");
40   private final Key<FileViewProvider> myPsiHardRefKey = Key.create("HARD_REFERENCE_TO_PSI"); //non-static!
41
42   private final PsiManagerImpl myManager;
43   private final NotNullLazyValue<? extends FileIndexFacade> myFileIndex;
44
45   private final AtomicReference<ConcurrentMap<VirtualFile, PsiDirectory>> myVFileToPsiDirMap = new AtomicReference<>();
46   private final AtomicReference<ConcurrentMap<VirtualFile, FileViewProvider>> myVFileToViewProviderMap = new AtomicReference<>();
47
48   /**
49    * Holds thread-local temporary providers that are sometimes needed while checking if a file is valid
50    */
51   private final ThreadLocal<Map<VirtualFile, FileViewProvider>> myTempProviders = ThreadLocal.withInitial(() -> new HashMap<>());
52
53   private boolean myDisposed;
54
55   private final MessageBusConnection myConnection;
56
57   public FileManagerImpl(@NotNull PsiManagerImpl manager, @NotNull NotNullLazyValue<? extends FileIndexFacade> fileIndex) {
58     myManager = manager;
59     myFileIndex = fileIndex;
60     myConnection = manager.getProject().getMessageBus().connect();
61
62     Disposer.register(manager.getProject(), this);
63     LowMemoryWatcher.register(this::processQueue, this);
64
65     myConnection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
66       @Override
67       public void enteredDumbMode() {
68         processFileTypesChanged();
69       }
70
71       @Override
72       public void exitDumbMode() {
73         processFileTypesChanged();
74       }
75     });
76   }
77
78   private static final VirtualFile NULL = new LightVirtualFile();
79
80   public void processQueue() {
81     // just to call processQueue()
82     ConcurrentMap<VirtualFile, FileViewProvider> map = myVFileToViewProviderMap.get();
83     if (map != null) {
84       map.remove(NULL);
85     }
86   }
87
88   @VisibleForTesting
89   @NotNull
90   public ConcurrentMap<VirtualFile, FileViewProvider> getVFileToViewProviderMap() {
91     ConcurrentMap<VirtualFile, FileViewProvider> map = myVFileToViewProviderMap.get();
92     if (map == null) {
93       map = ConcurrencyUtil.cacheOrGet(myVFileToViewProviderMap, ContainerUtil.createConcurrentWeakValueMap());
94     }
95     return map;
96   }
97
98   @NotNull
99   private ConcurrentMap<VirtualFile, PsiDirectory> getVFileToPsiDirMap() {
100     ConcurrentMap<VirtualFile, PsiDirectory> map = myVFileToPsiDirMap.get();
101     if (map == null) {
102       map = ConcurrencyUtil.cacheOrGet(myVFileToPsiDirMap, ContainerUtil.createConcurrentSoftValueMap());
103     }
104     return map;
105   }
106
107   public static void clearPsiCaches(@NotNull FileViewProvider provider) {
108     ((AbstractFileViewProvider)provider).getCachedPsiFiles().forEach(PsiFile::clearCaches);
109   }
110
111   public void forceReload(@NotNull VirtualFile vFile) {
112     LanguageSubstitutors.cancelReparsing(vFile);
113     FileViewProvider viewProvider = findCachedViewProvider(vFile);
114     if (viewProvider == null) {
115       return;
116     }
117     ApplicationManager.getApplication().assertWriteAccessAllowed();
118
119     VirtualFile dir = vFile.getParent();
120     PsiDirectory parentDir = dir == null ? null : getCachedDirectory(dir);
121     PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
122     if (parentDir == null) {
123       event.setPropertyName(PsiTreeChangeEvent.PROP_UNLOADED_PSI);
124
125       myManager.beforePropertyChange(event);
126       setViewProvider(vFile, null);
127       myManager.propertyChanged(event);
128     } else {
129       event.setParent(parentDir);
130
131       myManager.beforeChildrenChange(event);
132       setViewProvider(vFile, null);
133       myManager.childrenChanged(event);
134     }
135   }
136
137   public void firePropertyChangedForUnloadedPsi() {
138     PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
139     event.setPropertyName(PsiTreeChangeEvent.PROP_UNLOADED_PSI);
140
141     myManager.beforePropertyChange(event);
142     myManager.propertyChanged(event);
143   }
144
145   @Override
146   public void dispose() {
147     myConnection.disconnect();
148     clearViewProviders();
149
150     myDisposed = true;
151   }
152
153   private void clearViewProviders() {
154     ApplicationManager.getApplication().assertWriteAccessAllowed();
155     DebugUtil.performPsiModification("clearViewProviders", () -> {
156       ConcurrentMap<VirtualFile, FileViewProvider> map = myVFileToViewProviderMap.get();
157       if (map != null) {
158         for (final FileViewProvider provider : map.values()) {
159           markInvalidated(provider);
160         }
161       }
162       myVFileToViewProviderMap.set(null);
163     });
164   }
165
166   @Override
167   @TestOnly
168   public void cleanupForNextTest() {
169     ApplicationManager.getApplication().runWriteAction(this::clearViewProviders);
170
171     myVFileToPsiDirMap.set(null);
172     ((PsiModificationTrackerImpl)myManager.getModificationTracker()).incCounter();
173   }
174
175   @Override
176   @NotNull
177   public FileViewProvider findViewProvider(@NotNull final VirtualFile file) {
178     assert !file.isDirectory();
179     FileViewProvider viewProvider = findCachedViewProvider(file);
180     if (viewProvider != null) return viewProvider;
181     if (file instanceof VirtualFileWindow) {
182       throw new IllegalStateException("File " + file + " is invalid");
183     }
184
185     Map<VirtualFile, FileViewProvider> tempMap = myTempProviders.get();
186     if (tempMap.containsKey(file)) {
187       return Objects.requireNonNull(tempMap.get(file), "Recursive file view provider creation");
188     }
189
190     viewProvider = createFileViewProvider(file, true);
191     if (file instanceof LightVirtualFile) {
192       return file.putUserDataIfAbsent(myPsiHardRefKey, viewProvider);
193     }
194     return ConcurrencyUtil.cacheOrGet(getVFileToViewProviderMap(), file, viewProvider);
195   }
196
197   @Override
198   public FileViewProvider findCachedViewProvider(@NotNull final VirtualFile file) {
199     FileViewProvider viewProvider = getRawCachedViewProvider(file);
200
201     if (viewProvider instanceof AbstractFileViewProvider && viewProvider.getUserData(IN_COMA) != null) {
202       Map<VirtualFile, FileViewProvider> tempMap = myTempProviders.get();
203       if (tempMap.containsKey(file)) {
204         return tempMap.get(file);
205       }
206
207       if (!evaluateValidity((AbstractFileViewProvider)viewProvider)) {
208         return null;
209       }
210     }
211     return viewProvider;
212   }
213
214   @Nullable
215   private FileViewProvider getRawCachedViewProvider(@NotNull VirtualFile file) {
216     ConcurrentMap<VirtualFile, FileViewProvider> map = myVFileToViewProviderMap.get();
217     FileViewProvider viewProvider = map == null ? null : map.get(file);
218     return viewProvider == null ? file.getUserData(myPsiHardRefKey) : viewProvider;
219   }
220
221   @Override
222   public void setViewProvider(@NotNull final VirtualFile virtualFile, @Nullable final FileViewProvider fileViewProvider) {
223     FileViewProvider prev = getRawCachedViewProvider(virtualFile);
224     if (prev == fileViewProvider) return;
225     if (prev != null) {
226       DebugUtil.performPsiModification(null, () -> markInvalidated(prev));
227     }
228
229     if (fileViewProvider == null) {
230       getVFileToViewProviderMap().remove(virtualFile);
231     }
232     else if (virtualFile instanceof LightVirtualFile) {
233       virtualFile.putUserData(myPsiHardRefKey, fileViewProvider);
234     }
235     else {
236       getVFileToViewProviderMap().put(virtualFile, fileViewProvider);
237     }
238   }
239
240   @Override
241   @NotNull
242   public FileViewProvider createFileViewProvider(@NotNull final VirtualFile file, boolean eventSystemEnabled) {
243     FileType fileType = file.getFileType();
244     Language language = LanguageUtil.getLanguageForPsi(myManager.getProject(), file);
245     FileViewProviderFactory factory = language == null
246                                       ? FileTypeFileViewProviders.INSTANCE.forFileType(fileType)
247                                       : LanguageFileViewProviders.INSTANCE.forLanguage(language);
248     FileViewProvider viewProvider = factory == null ? null : factory.createFileViewProvider(file, language, myManager, eventSystemEnabled);
249
250     return viewProvider == null ? new SingleRootFileViewProvider(myManager, file, eventSystemEnabled, fileType) : viewProvider;
251   }
252
253   /** @deprecated Left for plugin compatibility */
254   @Deprecated
255   public void markInitialized() {
256   }
257
258   /** @deprecated Left for plugin compatibility */
259   @Deprecated
260   public boolean isInitialized() {
261     return true;
262   }
263
264   private boolean myProcessingFileTypesChange;
265
266   void processFileTypesChanged() {
267     if (myProcessingFileTypesChange) return;
268     myProcessingFileTypesChange = true;
269     DebugUtil.performPsiModification(null, () -> {
270       try {
271         ApplicationManager.getApplication().runWriteAction(() -> {
272           PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
273           event.setPropertyName(PsiTreeChangeEvent.PROP_FILE_TYPES);
274           myManager.beforePropertyChange(event);
275
276           possiblyInvalidatePhysicalPsi();
277
278           myManager.propertyChanged(event);
279         });
280       }
281       finally {
282         myProcessingFileTypesChange = false;
283       }
284     });
285   }
286
287   void possiblyInvalidatePhysicalPsi() {
288     ApplicationManager.getApplication().assertWriteAccessAllowed();
289     removeInvalidDirs();
290     for (FileViewProvider provider : getVFileToViewProviderMap().values()) {
291       markPossiblyInvalidated(provider);
292     }
293   }
294
295   void dispatchPendingEvents() {
296     if (myDisposed) {
297       LOG.error("Project is already disposed: "+myManager.getProject());
298     }
299
300     myConnection.deliverImmediately();
301   }
302
303   @TestOnly
304   void checkConsistency() {
305     for (VirtualFile file : new ArrayList<>(getVFileToViewProviderMap().keySet())) {
306       findCachedViewProvider(file); // complete delayed validity checks
307     }
308
309     Map<VirtualFile, FileViewProvider> fileToViewProvider = new HashMap<>(getVFileToViewProviderMap());
310     myVFileToViewProviderMap.set(null);
311     for (Map.Entry<VirtualFile, FileViewProvider> entry : fileToViewProvider.entrySet()) {
312       final FileViewProvider fileViewProvider = entry.getValue();
313       VirtualFile vFile = entry.getKey();
314       LOG.assertTrue(vFile.isValid());
315       PsiFile psiFile1 = findFile(vFile);
316       if (psiFile1 != null && fileViewProvider != null && fileViewProvider.isPhysical()) { // might get collected
317         PsiFile psi = fileViewProvider.getPsi(fileViewProvider.getBaseLanguage());
318         assert psi != null : fileViewProvider +"; "+fileViewProvider.getBaseLanguage()+"; "+psiFile1;
319         assert psiFile1.getClass().equals(psi.getClass()) : psiFile1 +"; "+psi + "; "+psiFile1.getClass() +"; "+psi.getClass();
320       }
321     }
322
323     Map<VirtualFile, PsiDirectory> fileToPsiDirMap = new HashMap<>(getVFileToPsiDirMap());
324     myVFileToPsiDirMap.set(null);
325
326     for (VirtualFile vFile : fileToPsiDirMap.keySet()) {
327       LOG.assertTrue(vFile.isValid());
328       PsiDirectory psiDir1 = findDirectory(vFile);
329       LOG.assertTrue(psiDir1 != null);
330
331       VirtualFile parent = vFile.getParent();
332       if (parent != null) {
333         LOG.assertTrue(getVFileToPsiDirMap().get(parent) != null);
334       }
335     }
336   }
337
338   @Override
339   @Nullable
340   public PsiFile findFile(@NotNull VirtualFile vFile) {
341     if (vFile.isDirectory()) return null;
342
343     ApplicationManager.getApplication().assertReadAccessAllowed();
344     if (!vFile.isValid()) {
345       LOG.error("Invalid file: " + vFile);
346       return null;
347     }
348
349     dispatchPendingEvents();
350     final FileViewProvider viewProvider = findViewProvider(vFile);
351     return viewProvider.getPsi(viewProvider.getBaseLanguage());
352   }
353
354   @Override
355   @Nullable
356   public PsiFile getCachedPsiFile(@NotNull VirtualFile vFile) {
357     ApplicationManager.getApplication().assertReadAccessAllowed();
358     if (!vFile.isValid()) throw new InvalidVirtualFileAccessException(vFile);
359     if (myDisposed) {
360       LOG.error("Project is already disposed: " + myManager.getProject());
361     }
362
363     dispatchPendingEvents();
364
365     return getCachedPsiFileInner(vFile);
366   }
367
368   @Override
369   @Nullable
370   public PsiDirectory findDirectory(@NotNull VirtualFile vFile) {
371     if (myDisposed) {
372       LOG.error("Access to psi files should not be performed after project disposal: "+myManager.getProject());
373     }
374
375
376     ApplicationManager.getApplication().assertReadAccessAllowed();
377     if (!vFile.isValid()) {
378       LOG.error("File is not valid:" + vFile);
379       return null;
380     }
381
382     if (!vFile.isDirectory()) return null;
383     dispatchPendingEvents();
384
385     return findDirectoryImpl(vFile, getVFileToPsiDirMap());
386   }
387
388   @Nullable
389   private PsiDirectory findDirectoryImpl(@NotNull VirtualFile vFile, @NotNull ConcurrentMap<VirtualFile, PsiDirectory> psiDirMap) {
390     PsiDirectory psiDir = psiDirMap.get(vFile);
391     if (psiDir != null) return psiDir;
392
393     if (isExcludedOrIgnored(vFile)) return null;
394
395     VirtualFile parent = vFile.getParent();
396     if (parent != null) { //?
397       findDirectoryImpl(parent, psiDirMap);// need to cache parent directory - used for firing events
398     }
399
400     psiDir = PsiDirectoryFactory.getInstance(myManager.getProject()).createDirectory(vFile);
401     return ConcurrencyUtil.cacheOrGet(psiDirMap, vFile, psiDir);
402   }
403
404   private boolean isExcludedOrIgnored(@NotNull VirtualFile vFile) {
405     if (myManager.getProject().isDefault()) return false;
406     FileIndexFacade fileIndexFacade = myFileIndex.getValue();
407     return Registry.is("ide.hide.excluded.files") ? fileIndexFacade.isExcludedFile(vFile) : fileIndexFacade.isUnderIgnored(vFile);
408   }
409
410   public PsiDirectory getCachedDirectory(@NotNull VirtualFile vFile) {
411     return getVFileToPsiDirMap().get(vFile);
412   }
413
414   void removeFilesAndDirsRecursively(@NotNull VirtualFile vFile) {
415     DebugUtil.performPsiModification("removeFilesAndDirsRecursively", () -> {
416       VfsUtilCore.visitChildrenRecursively(vFile, new VirtualFileVisitor<Void>() {
417         @Override
418         public boolean visitFile(@NotNull VirtualFile file) {
419           if (file.isDirectory()) {
420             getVFileToPsiDirMap().remove(file);
421           }
422           else {
423             FileViewProvider viewProvider = getVFileToViewProviderMap().remove(file);
424             if (viewProvider != null) {
425               markInvalidated(viewProvider);
426             }
427           }
428           return true;
429         }
430       });
431     });
432   }
433
434   private void markInvalidated(@NotNull FileViewProvider viewProvider) {
435     viewProvider.putUserData(IN_COMA, null);
436     ((AbstractFileViewProvider)viewProvider).markInvalidated();
437     viewProvider.getVirtualFile().putUserData(myPsiHardRefKey, null);
438   }
439
440   public static void markPossiblyInvalidated(@NotNull FileViewProvider viewProvider) {
441     LOG.assertTrue(!(viewProvider instanceof FreeThreadedFileViewProvider));
442     viewProvider.putUserData(IN_COMA, true);
443     ((AbstractFileViewProvider)viewProvider).markPossiblyInvalidated();
444     clearPsiCaches(viewProvider);
445   }
446
447   @Nullable
448   PsiFile getCachedPsiFileInner(@NotNull VirtualFile file) {
449     FileViewProvider fileViewProvider = findCachedViewProvider(file);
450     return fileViewProvider != null ? ((AbstractFileViewProvider)fileViewProvider).getCachedPsi(fileViewProvider.getBaseLanguage()) : null;
451   }
452
453   @NotNull
454   @Override
455   public List<PsiFile> getAllCachedFiles() {
456     List<PsiFile> files = new ArrayList<>();
457     for (VirtualFile file : new ArrayList<>(getVFileToViewProviderMap().keySet())) {
458       FileViewProvider provider = findCachedViewProvider(file);
459       if (provider != null) {
460         ContainerUtil.addAllNotNull(files, ((AbstractFileViewProvider)provider).getCachedPsiFiles());
461       }
462     }
463     return files;
464   }
465
466   private void removeInvalidDirs() {
467     myVFileToPsiDirMap.set(null);
468   }
469
470   void removeInvalidFilesAndDirs(boolean useFind) {
471     removeInvalidDirs();
472
473     // note: important to update directories map first - findFile uses findDirectory!
474     Map<VirtualFile, FileViewProvider> fileToPsiFileMap = new THashMap<>(getVFileToViewProviderMap());
475     Map<VirtualFile, FileViewProvider> originalFileToPsiFileMap = new THashMap<>(getVFileToViewProviderMap());
476     if (useFind) {
477       myVFileToViewProviderMap.set(null);
478     }
479     for (Iterator<VirtualFile> iterator = fileToPsiFileMap.keySet().iterator(); iterator.hasNext();) {
480       VirtualFile vFile = iterator.next();
481
482       if (!vFile.isValid()) {
483         iterator.remove();
484         continue;
485       }
486
487       FileViewProvider view = fileToPsiFileMap.get(vFile);
488       if (useFind) {
489         if (view == null) { // soft ref. collected
490           iterator.remove();
491           continue;
492         }
493         PsiFile psiFile1 = findFile(vFile);
494         if (psiFile1 == null) {
495           iterator.remove();
496           continue;
497         }
498
499         if (!areViewProvidersEquivalent(view, psiFile1.getViewProvider())) {
500           iterator.remove();
501         }
502         else {
503           clearPsiCaches(view);
504         }
505       }
506       else if (!evaluateValidity((AbstractFileViewProvider)view)) {
507         iterator.remove();
508       }
509     }
510     myVFileToViewProviderMap.set(null);
511     getVFileToViewProviderMap().putAll(fileToPsiFileMap);
512
513     markInvalidations(originalFileToPsiFileMap);
514   }
515
516   static boolean areViewProvidersEquivalent(@NotNull FileViewProvider view1, @NotNull FileViewProvider view2) {
517     if (view1.getClass() != view2.getClass() || view1.getFileType() != view2.getFileType()) return false;
518
519     Language baseLanguage = view1.getBaseLanguage();
520     if (baseLanguage != view2.getBaseLanguage()) return false;
521
522     if (!view1.getLanguages().equals(view2.getLanguages())) return false;
523     PsiFile psi1 = view1.getPsi(baseLanguage);
524     PsiFile psi2 = view2.getPsi(baseLanguage);
525     if (psi1 == null || psi2 == null) return psi1 == psi2;
526     return psi1.getClass() == psi2.getClass();
527   }
528
529   private void markInvalidations(@NotNull Map<VirtualFile, FileViewProvider> originalFileToPsiFileMap) {
530     if (!originalFileToPsiFileMap.isEmpty()) {
531       DebugUtil.performPsiModification(null, ()->{
532         for (Map.Entry<VirtualFile, FileViewProvider> entry : originalFileToPsiFileMap.entrySet()) {
533           FileViewProvider viewProvider = entry.getValue();
534           if (getVFileToViewProviderMap().get(entry.getKey()) != viewProvider) {
535             markInvalidated(viewProvider);
536           }
537         }
538       });
539     }
540   }
541
542   @Override
543   public void reloadFromDisk(@NotNull PsiFile file) {
544     ApplicationManager.getApplication().assertWriteAccessAllowed();
545     VirtualFile vFile = file.getVirtualFile();
546     assert vFile != null;
547
548     Document document = FileDocumentManager.getInstance().getCachedDocument(vFile);
549     if (document != null) {
550       FileDocumentManager.getInstance().reloadFromDisk(document);
551     }
552     else {
553       reloadPsiAfterTextChange(file.getViewProvider(), vFile);
554     }
555   }
556
557   void reloadPsiAfterTextChange(@NotNull FileViewProvider viewProvider, @NotNull VirtualFile vFile) {
558     if (!areViewProvidersEquivalent(viewProvider, createFileViewProvider(vFile, false))) {
559       forceReload(vFile);
560       return;
561     }
562
563     ((AbstractFileViewProvider)viewProvider).onContentReload();
564   }
565
566   /**
567    * Should be called only from implementations of {@link PsiFile#isValid()}, only after they've been {@link PsiFileEx#markInvalidated()},
568    * and only to check if they can be made valid again.
569    * Synchronized by read-write action. Calls from several threads in read action for the same virtual file are allowed.
570    * @return if the file is still valid
571    */
572   public boolean evaluateValidity(@NotNull PsiFile file) {
573     AbstractFileViewProvider vp = (AbstractFileViewProvider)file.getViewProvider();
574     return evaluateValidity(vp) && vp.getCachedPsiFiles().contains(file);
575   }
576
577   private boolean evaluateValidity(@NotNull AbstractFileViewProvider viewProvider) {
578     ApplicationManager.getApplication().assertReadAccessAllowed();
579
580     VirtualFile file = viewProvider.getVirtualFile();
581     if (getRawCachedViewProvider(file) != viewProvider) {
582       return false;
583     }
584
585     if (viewProvider.getUserData(IN_COMA) == null) {
586       return true;
587     }
588
589     if (shouldResurrect(viewProvider, file)) {
590       viewProvider.putUserData(IN_COMA, null);
591       LOG.assertTrue(getRawCachedViewProvider(file) == viewProvider);
592
593       for (PsiFile psiFile : viewProvider.getCachedPsiFiles()) {
594         // update "myPossiblyInvalidated" fields in files by calling "isValid"
595         // that will call us recursively again, but since we're not IN_COMA now, we'll exit earlier and avoid SOE
596         if (!psiFile.isValid()) {
597           LOG.error(new PsiInvalidElementAccessException(psiFile));
598         }
599       }
600       return true;
601     }
602
603     getVFileToViewProviderMap().remove(file, viewProvider);
604     file.replace(myPsiHardRefKey, viewProvider, null);
605     viewProvider.putUserData(IN_COMA, null);
606
607     return false;
608   }
609
610   private boolean shouldResurrect(@NotNull FileViewProvider viewProvider, @NotNull VirtualFile file) {
611     if (!file.isValid()) return false;
612
613     Map<VirtualFile, FileViewProvider> tempProviders = myTempProviders.get();
614     if (tempProviders.containsKey(file)) {
615       LOG.error(new StackOverflowPreventedException("isValid leads to endless recursion in " + viewProvider.getClass() + ": " + new ArrayList<>(viewProvider.getLanguages())));
616     }
617     tempProviders.put(file, null);
618     try {
619       FileViewProvider recreated = createFileViewProvider(file, true);
620       tempProviders.put(file, recreated);
621       return areViewProvidersEquivalent(viewProvider, recreated) &&
622              ((AbstractFileViewProvider)viewProvider).getCachedPsiFiles().stream().noneMatch(f -> hasInvalidOriginal(f));
623     }
624     finally {
625       FileViewProvider temp = tempProviders.remove(file);
626       if (temp != null) {
627         DebugUtil.performPsiModification("invalidate temp view provider", () -> ((AbstractFileViewProvider)temp).markInvalidated());
628       }
629     }
630   }
631
632   private static boolean hasInvalidOriginal(@NotNull PsiFile file) {
633     PsiFile original = file.getOriginalFile();
634     return original != file && !original.isValid();
635   }
636
637 }