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