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