fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / core-impl / src / com / intellij / psi / AbstractFileViewProvider.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;
3
4 import com.intellij.injected.editor.DocumentWindow;
5 import com.intellij.injected.editor.VirtualFileWindow;
6 import com.intellij.lang.ASTNode;
7 import com.intellij.lang.Language;
8 import com.intellij.lang.LanguageParserDefinitions;
9 import com.intellij.lang.ParserDefinition;
10 import com.intellij.openapi.application.ApplicationManager;
11 import com.intellij.openapi.application.impl.ApplicationInfoImpl;
12 import com.intellij.openapi.command.undo.UndoConstants;
13 import com.intellij.openapi.diagnostic.Attachment;
14 import com.intellij.openapi.diagnostic.Logger;
15 import com.intellij.openapi.editor.Document;
16 import com.intellij.openapi.fileEditor.FileDocumentManager;
17 import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
18 import com.intellij.openapi.fileTypes.FileType;
19 import com.intellij.openapi.fileTypes.FileTypeRegistry;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.roots.FileIndexFacade;
22 import com.intellij.openapi.util.Key;
23 import com.intellij.openapi.util.UserDataHolderBase;
24 import com.intellij.openapi.vfs.NonPhysicalFileSystem;
25 import com.intellij.openapi.vfs.VFileProperty;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.psi.impl.*;
28 import com.intellij.psi.impl.file.PsiBinaryFileImpl;
29 import com.intellij.psi.impl.file.PsiLargeBinaryFileImpl;
30 import com.intellij.psi.impl.file.PsiLargeTextFileImpl;
31 import com.intellij.psi.impl.file.impl.FileManager;
32 import com.intellij.psi.impl.file.impl.FileManagerImpl;
33 import com.intellij.psi.impl.source.PsiFileImpl;
34 import com.intellij.psi.impl.source.PsiPlainTextFileImpl;
35 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
36 import com.intellij.psi.impl.source.tree.FileElement;
37 import com.intellij.psi.util.PsiUtilCore;
38 import com.intellij.testFramework.LightVirtualFile;
39 import com.intellij.util.LocalTimeCounter;
40 import com.intellij.util.containers.ContainerUtil;
41 import org.jetbrains.annotations.NonNls;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import java.lang.ref.Reference;
46 import java.lang.ref.SoftReference;
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.List;
50 import java.util.Set;
51 import java.util.function.Consumer;
52
53 public abstract class AbstractFileViewProvider extends UserDataHolderBase implements FileViewProvider {
54   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.AbstractFileViewProvider");
55   public static final Key<Object> FREE_THREADED = Key.create("FREE_THREADED");
56   private static final Key<Set<AbstractFileViewProvider>> KNOWN_COPIES = Key.create("KNOWN_COPIES");
57   @NotNull
58   private final PsiManagerEx myManager;
59   @NotNull
60   private final VirtualFile myVirtualFile;
61   private final boolean myEventSystemEnabled;
62   private final boolean myPhysical;
63   private volatile Content myContent;
64   private volatile Reference<Document> myDocument;
65   @NotNull
66   private final FileType myFileType;
67   private final PsiLock myPsiLock = new PsiLock();
68
69   protected AbstractFileViewProvider(@NotNull PsiManager manager,
70                                      @NotNull VirtualFile virtualFile,
71                                      boolean eventSystemEnabled,
72                                      @NotNull FileType type) {
73     myManager = (PsiManagerEx)manager;
74     myVirtualFile = virtualFile;
75     myEventSystemEnabled = eventSystemEnabled;
76     setContent(new VirtualFileContent());
77     myPhysical = isEventSystemEnabled() &&
78                  !(virtualFile instanceof LightVirtualFile) &&
79                  !(virtualFile.getFileSystem() instanceof NonPhysicalFileSystem);
80     virtualFile.putUserData(FREE_THREADED, isFreeThreaded(this));
81     myFileType = type;
82     if (virtualFile instanceof VirtualFileWindow && !(this instanceof FreeThreadedFileViewProvider)) {
83       throw new IllegalArgumentException("Must not create "+getClass()+" for injected file "+virtualFile+"; InjectedFileViewProvider must be used instead");
84     }
85   }
86
87   final boolean shouldCreatePsi() {
88     if (isIgnored()) return false;
89
90     VirtualFile vFile = getVirtualFile();
91     if (isPhysical() && vFile.isInLocalFileSystem()) { // check directories consistency
92       VirtualFile parent = vFile.getParent();
93       if (parent == null) return false;
94
95       PsiDirectory psiDir = getManager().findDirectory(parent);
96       if (psiDir == null) {
97         FileIndexFacade indexFacade = FileIndexFacade.getInstance(getManager().getProject());
98         if (!indexFacade.isInLibrarySource(vFile) && !indexFacade.isInLibraryClasses(vFile)) {
99           return false;
100         }
101       }
102     }
103     return true;
104   }
105
106   public static boolean isFreeThreaded(@NotNull FileViewProvider provider) {
107     return provider.getVirtualFile() instanceof LightVirtualFile && !provider.isEventSystemEnabled();
108   }
109
110   @NotNull
111   public PsiLock getFilePsiLock() {
112     return myPsiLock;
113   }
114
115   protected final boolean isIgnored() {
116     final VirtualFile file = getVirtualFile();
117     return !(file instanceof LightVirtualFile) && FileTypeRegistry.getInstance().isFileIgnored(file);
118   }
119
120   @Nullable
121   protected PsiFile createFile(@NotNull Project project, @NotNull VirtualFile file, @NotNull FileType fileType) {
122     return createFile(file, fileType, getBaseLanguage());
123   }
124
125   @NotNull
126   protected PsiFile createFile(@NotNull VirtualFile file, @NotNull FileType fileType, @NotNull Language language) {
127     if (fileType.isBinary() || file.is(VFileProperty.SPECIAL)) {
128       return SingleRootFileViewProvider.isTooLargeForContentLoading(file) ?
129              new PsiLargeBinaryFileImpl((PsiManagerImpl)getManager(), this) :
130              new PsiBinaryFileImpl((PsiManagerImpl)getManager(), this);
131     }
132     if (!SingleRootFileViewProvider.isTooLargeForIntelligence(file)) {
133       final PsiFile psiFile = createFile(language);
134       if (psiFile != null) return psiFile;
135     }
136
137     if (SingleRootFileViewProvider.isTooLargeForContentLoading(file)) {
138       return new PsiLargeTextFileImpl(this);
139     }
140
141     return new PsiPlainTextFileImpl(this);
142   }
143
144   @Nullable
145   protected PsiFile createFile(@NotNull Language lang) {
146     if (lang != getBaseLanguage()) return null;
147     final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang);
148     if (parserDefinition != null) {
149       return parserDefinition.createFile(this);
150     }
151     return null;
152   }
153
154   @Override
155   @NotNull
156   public final PsiManagerEx getManager() {
157     return myManager;
158   }
159
160   @Override
161   @NotNull
162   public CharSequence getContents() {
163     return getContent().getText();
164   }
165
166   @Override
167   @NotNull
168   public VirtualFile getVirtualFile() {
169     return myVirtualFile;
170   }
171
172   @Nullable
173   private Document getCachedDocument() {
174     final Document document = com.intellij.reference.SoftReference.dereference(myDocument);
175     if (document != null) return document;
176     return FileDocumentManager.getInstance().getCachedDocument(getVirtualFile());
177   }
178
179   @Override
180   public Document getDocument() {
181     Document document = com.intellij.reference.SoftReference.dereference(myDocument);
182     if (document == null) {
183       document = FileDocumentManager.getInstance().getDocument(getVirtualFile());
184       myDocument = document == null ? null : new SoftReference<>(document);
185     }
186     return document;
187   }
188
189   @Override
190   @Nullable
191   public final PsiFile getPsi(@NotNull Language target) {
192     if (!isPhysical()) {
193       FileManager fileManager = getManager().getFileManager();
194       VirtualFile virtualFile = getVirtualFile();
195       if (fileManager.findCachedViewProvider(virtualFile) == null && getCachedPsiFiles().isEmpty()) {
196         fileManager.setViewProvider(virtualFile, this);
197       }
198     }
199     return getPsiInner(target);
200   }
201
202   @Nullable
203   protected abstract PsiFile getPsiInner(Language target);
204
205   @SuppressWarnings("MethodDoesntCallSuperMethod")
206   @Override
207   public FileViewProvider clone() {
208     VirtualFile origFile = getVirtualFile();
209     LightVirtualFile copy = new LightVirtualFile(origFile.getName(), myFileType, getContents(), origFile.getCharset(), getModificationStamp());
210     origFile.copyCopyableDataTo(copy);
211     copy.setOriginalFile(origFile);
212     copy.putUserData(UndoConstants.DONT_RECORD_UNDO, Boolean.TRUE);
213     copy.setCharset(origFile.getCharset());
214
215     return createCopy(copy);
216   }
217
218   @Override
219   public PsiElement findElementAt(final int offset, @NotNull final Language language) {
220     final PsiFile psiFile = getPsi(language);
221     return psiFile != null ? findElementAt(psiFile, offset) : null;
222   }
223
224   @Override
225   @Nullable
226   public PsiReference findReferenceAt(final int offset, @NotNull final Language language) {
227     final PsiFile psiFile = getPsi(language);
228     return psiFile != null ? findReferenceAt(psiFile, offset) : null;
229   }
230
231   @Nullable
232   protected static PsiReference findReferenceAt(@Nullable final PsiFile psiFile, final int offset) {
233     if (psiFile == null) return null;
234     int offsetInElement = offset;
235     PsiElement child = psiFile.getFirstChild();
236     while (child != null) {
237       final int length = child.getTextLength();
238       if (length <= offsetInElement) {
239         offsetInElement -= length;
240         child = child.getNextSibling();
241         continue;
242       }
243       return child.findReferenceAt(offsetInElement);
244     }
245     return null;
246   }
247
248   @Nullable
249   public static PsiElement findElementAt(@Nullable PsiElement psiFile, final int offset) {
250     ASTNode node = psiFile == null ? null : psiFile.getNode();
251     return node == null ? null : SourceTreeToPsiMap.treeElementToPsi(node.findLeafElementAt(offset));
252   }
253
254     @Override
255   public void beforeContentsSynchronized() {
256   }
257
258   @Override
259   public void contentsSynchronized() {
260     if (myContent instanceof PsiFileContent) {
261       setContent(new VirtualFileContent());
262     }
263     checkLengthConsistency();
264   }
265
266   public final void onContentReload() {
267     List<PsiFile> files = getCachedPsiFiles();
268     List<PsiTreeChangeEventImpl> events = new ArrayList<>(files.size());
269     List<PsiTreeChangeEventImpl> genericEvents = new ArrayList<>(files.size());
270     for (PsiFile file : files) {
271       genericEvents.add(createChildrenChangeEvent(file, true));
272       events.add(createChildrenChangeEvent(file, false));
273     }
274
275     beforeContentsSynchronized();
276
277     for (PsiTreeChangeEventImpl event : genericEvents) {
278       ((PsiManagerImpl)getManager()).beforeChildrenChange(event);
279     }
280     for (PsiTreeChangeEventImpl event : events) {
281       ((PsiManagerImpl)getManager()).beforeChildrenChange(event);
282     }
283
284     for (PsiFile psiFile : files) {
285       if (psiFile instanceof PsiFileEx) {
286         ((PsiFileEx)psiFile).onContentReload();
287       }
288     }
289
290     contentsSynchronized();
291
292     for (PsiTreeChangeEventImpl event : events) {
293       ((PsiManagerImpl)getManager()).childrenChanged(event);
294     }
295     for (PsiTreeChangeEventImpl event : genericEvents) {
296       ((PsiManagerImpl)getManager()).childrenChanged(event);
297     }
298   }
299
300   private PsiTreeChangeEventImpl createChildrenChangeEvent(PsiFile file, boolean generic) {
301     PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
302     event.setParent(file);
303     event.setFile(file);
304     event.setGenericChange(generic);
305     if (file instanceof PsiFileImpl && ((PsiFileImpl)file).isContentsLoaded()) {
306       event.setOffset(0);
307       event.setOldLength(file.getTextLength());
308     }
309     return event;
310   }
311
312   @Override
313   public void rootChanged(@NotNull PsiFile psiFile) {
314     if (psiFile instanceof PsiFileImpl && ((PsiFileImpl)psiFile).isContentsLoaded() && psiFile.isValid()) {
315       setContent(new PsiFileContent(((PsiFileImpl)psiFile).calcTreeElement(), LocalTimeCounter.currentTime()));
316     }
317   }
318
319   @Override
320   public boolean isEventSystemEnabled() {
321     return myEventSystemEnabled;
322   }
323
324   @Override
325   public boolean isPhysical() {
326     return myPhysical;
327   }
328
329   @Override
330   public long getModificationStamp() {
331     return getContent().getModificationStamp();
332   }
333
334   @Override
335   public boolean supportsIncrementalReparse(@NotNull final Language rootLanguage) {
336     return true;
337   }
338
339   @NotNull
340   private Content getContent() {
341     return myContent;
342   }
343
344   private void setContent(@NotNull Content content) {
345     myContent = content;
346   }
347
348   private void checkLengthConsistency() {
349     Document document = getCachedDocument();
350     if (document instanceof DocumentWindow) {
351       return;
352     }
353     if (document != null &&
354         ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(myManager.getProject())).getSynchronizer().isInSynchronization(document)) {
355       return;
356     }
357
358     List<FileElement> knownTreeRoots = getKnownTreeRoots();
359     if (knownTreeRoots.isEmpty()) return;
360
361     int fileLength = myContent.getTextLength();
362     for (FileElement fileElement : knownTreeRoots) {
363       int nodeLength = fileElement.getTextLength();
364       if (!isDocumentConsistentWithPsi(fileLength, fileElement, nodeLength)) {
365         PsiUtilCore.ensureValid(fileElement.getPsi());
366         List<Attachment> attachments = ContainerUtil.newArrayList(new Attachment(myVirtualFile.getName(), myContent.getText().toString()),
367                                                                   new Attachment(myVirtualFile.getNameWithoutExtension() + ".tree.txt", fileElement.getText()));
368         if (document != null) {
369           attachments.add(new Attachment(myVirtualFile.getNameWithoutExtension() + ".document.txt", document.getText()));
370         }
371         // exceptions here should be assigned to peter
372         LOG.error("Inconsistent " + fileElement.getElementType() + " tree in " + this + "; nodeLength=" + nodeLength + "; fileLength=" + fileLength,
373                   attachments.toArray(Attachment.EMPTY_ARRAY));
374       }
375     }
376   }
377
378   private boolean isDocumentConsistentWithPsi(int fileLength, FileElement fileElement, int nodeLength) {
379     if (nodeLength != fileLength) return false;
380
381     if (ApplicationManager.getApplication().isUnitTestMode() && !ApplicationInfoImpl.isInStressTest()) {
382       return fileElement.textMatches(myContent.getText());
383     }
384
385     return true;
386   }
387
388
389   @NonNls
390   @Override
391   public String toString() {
392     return getClass().getName() + "{myVirtualFile=" + myVirtualFile + ", content=" + getContent() + '}';
393   }
394
395   public abstract PsiFile getCachedPsi(@NotNull Language target);
396
397   @NotNull
398   public abstract List<PsiFile> getCachedPsiFiles();
399
400   @NotNull
401   public abstract List<FileElement> getKnownTreeRoots();
402
403   public final void markInvalidated() {
404     invalidateCachedPsi();
405     forKnownCopies(copy -> myManager.getFileManager().setViewProvider(copy.getVirtualFile(), null));
406   }
407
408   public final void markPossiblyInvalidated() {
409     invalidateCachedPsi();
410     forKnownCopies(FileManagerImpl::markPossiblyInvalidated);
411   }
412
413   private void invalidateCachedPsi() {
414     for (PsiFile file : getCachedPsiFiles()) {
415       if (file instanceof PsiFileEx) {
416         ((PsiFileEx)file).markInvalidated();
417       }
418     }
419   }
420
421   private void forKnownCopies(Consumer<? super AbstractFileViewProvider> action) {
422     Set<AbstractFileViewProvider> knownCopies = getUserData(KNOWN_COPIES);
423     if (knownCopies != null) {
424       for (AbstractFileViewProvider copy : knownCopies) {
425         if (copy.getCachedPsiFiles().stream().anyMatch(f -> f.getOriginalFile().getViewProvider() == this)) {
426           action.accept(copy);
427         }
428       }
429     }
430   }
431
432   public final void registerAsCopy(@NotNull AbstractFileViewProvider copy) {
433     if (copy instanceof FreeThreadedFileViewProvider) {
434       LOG.assertTrue(this instanceof FreeThreadedFileViewProvider, "Injected file can't have non-injected original file");
435     }
436     Set<AbstractFileViewProvider> copies = getUserData(KNOWN_COPIES);
437     if (copies == null) {
438       copies = putUserDataIfAbsent(KNOWN_COPIES, Collections.newSetFromMap(ContainerUtil.createConcurrentWeakMap()));
439     }
440     if (copy.getUserData(KNOWN_COPIES) != null) {
441       LOG.error("A view provider copy must be registered before it may have its own copies, to avoid cycles");
442     }
443     copies.add(copy);
444   }
445
446   private interface Content {
447     @NotNull
448     CharSequence getText();
449     int getTextLength();
450
451     long getModificationStamp();
452   }
453
454   private class VirtualFileContent implements Content {
455     @NotNull
456     @Override
457     public CharSequence getText() {
458       final VirtualFile virtualFile = getVirtualFile();
459       if (virtualFile instanceof LightVirtualFile) {
460         Document doc = getCachedDocument();
461         if (doc != null) return getLastCommittedText(doc);
462         return ((LightVirtualFile)virtualFile).getContent();
463       }
464
465       final Document document = getDocument();
466       if (document == null) {
467         return LoadTextUtil.loadText(virtualFile);
468       }
469       return getLastCommittedText(document);
470     }
471
472     @Override
473     public int getTextLength() {
474       return getText().length();
475     }
476
477     @Override
478     public long getModificationStamp() {
479       final Document document = getCachedDocument();
480       if (document == null) {
481         return getVirtualFile().getModificationStamp();
482       }
483       return getLastCommittedStamp(document);
484     }
485
486     @NonNls
487     @Override
488     public String toString() {
489       return "VirtualFileContent{size=" + getVirtualFile().getLength() + "}";
490     }
491   }
492
493   @NotNull
494   private CharSequence getLastCommittedText(@NotNull Document document) {
495     return PsiDocumentManager.getInstance(myManager.getProject()).getLastCommittedText(document);
496   }
497   private long getLastCommittedStamp(@NotNull Document document) {
498     return PsiDocumentManager.getInstance(myManager.getProject()).getLastCommittedStamp(document);
499   }
500
501   private static class PsiFileContent implements Content {
502     private final long myModificationStamp;
503     private final FileElement myFileElement;
504
505     PsiFileContent(@NotNull FileElement fileElement, long modificationStamp) {
506       myModificationStamp = modificationStamp;
507       myFileElement = fileElement;
508     }
509
510     @NotNull
511     @Override
512     public CharSequence getText() {
513       return myFileElement.getText();
514     }
515
516     @Override
517     public int getTextLength() {
518       return myFileElement.getTextLength();
519     }
520
521     @Override
522     public long getModificationStamp() {
523       return myModificationStamp;
524     }
525   }
526
527   @NotNull
528   @Override
529   public PsiFile getStubBindingRoot() {
530     final PsiFile psi = getPsi(getBaseLanguage());
531     assert psi != null;
532     return psi;
533   }
534
535   @NotNull
536   @Override
537   public final FileType getFileType() {
538     return myFileType;
539   }
540 }