fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / core-impl / src / com / intellij / psi / impl / PsiManagerImpl.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;
3
4 import com.intellij.lang.PsiBuilderFactory;
5 import com.intellij.openapi.Disposable;
6 import com.intellij.openapi.application.ApplicationManager;
7 import com.intellij.openapi.application.WriteAction;
8 import com.intellij.openapi.diagnostic.Logger;
9 import com.intellij.openapi.progress.ProgressIndicator;
10 import com.intellij.openapi.progress.ProgressIndicatorProvider;
11 import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
12 import com.intellij.openapi.progress.util.ProgressWrapper;
13 import com.intellij.openapi.project.Project;
14 import com.intellij.openapi.roots.FileIndexFacade;
15 import com.intellij.openapi.util.Disposer;
16 import com.intellij.openapi.util.NotNullLazyValue;
17 import com.intellij.openapi.vfs.NonPhysicalFileSystem;
18 import com.intellij.openapi.vfs.VirtualFile;
19 import com.intellij.openapi.vfs.VirtualFileFilter;
20 import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
21 import com.intellij.psi.*;
22 import com.intellij.psi.impl.file.impl.FileManager;
23 import com.intellij.psi.impl.file.impl.FileManagerImpl;
24 import com.intellij.psi.util.PsiModificationTracker;
25 import com.intellij.util.containers.ContainerUtil;
26 import com.intellij.util.messages.Topic;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
29 import org.jetbrains.annotations.TestOnly;
30
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.concurrent.atomic.AtomicInteger;
34 import java.util.concurrent.atomic.AtomicLong;
35
36 public final class PsiManagerImpl extends PsiManagerEx {
37   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiManagerImpl");
38
39   private final Project myProject;
40   private final NotNullLazyValue<? extends FileIndexFacade> myFileIndex;
41   private final PsiModificationTracker myModificationTracker;
42
43   private final FileManagerImpl myFileManager;
44
45   private final List<PsiTreeChangePreprocessor> myTreeChangePreprocessors = ContainerUtil.createLockFreeCopyOnWriteList();
46   private final List<PsiTreeChangeListener> myTreeChangeListeners = ContainerUtil.createLockFreeCopyOnWriteList();
47   private boolean myTreeChangeEventIsFiring;
48
49   private boolean myIsDisposed;
50
51   private VirtualFileFilter myAssertOnFileLoadingFilter = VirtualFileFilter.NONE;
52
53   private final AtomicInteger myBatchFilesProcessingModeCount = new AtomicInteger(0);
54
55   public static final Topic<AnyPsiChangeListener> ANY_PSI_CHANGE_TOPIC =
56     Topic.create("ANY_PSI_CHANGE_TOPIC", AnyPsiChangeListener.class, Topic.BroadcastDirection.TO_PARENT);
57
58   public PsiManagerImpl(Project project) {
59     // we need to initialize PsiBuilderFactory service so it won't initialize under PsiLock from ChameleonTransform
60     PsiBuilderFactory.getInstance();
61
62     myProject = project;
63     myFileIndex = NotNullLazyValue.createValue(() -> FileIndexFacade.getInstance(project));
64     myModificationTracker = PsiModificationTracker.SERVICE.getInstance(project);
65
66     myFileManager = new FileManagerImpl(this, myFileIndex);
67
68     myTreeChangePreprocessors.add((PsiTreeChangePreprocessor)myModificationTracker);
69
70     Disposer.register(project, () -> myIsDisposed = true);
71   }
72
73   @Override
74   public boolean isDisposed() {
75     return myIsDisposed;
76   }
77
78   @Override
79   public void dropResolveCaches() {
80     myFileManager.processQueue();
81     beforeChange(true);
82   }
83
84   @Override
85   public void dropPsiCaches() {
86     dropResolveCaches();
87     WriteAction.run(myFileManager::firePropertyChangedForUnloadedPsi);
88   }
89
90   @Override
91   public boolean isInProject(@NotNull PsiElement element) {
92     if (element instanceof PsiDirectoryContainer) {
93       PsiDirectory[] dirs = ((PsiDirectoryContainer)element).getDirectories();
94       for (PsiDirectory dir : dirs) {
95         if (!isInProject(dir)) return false;
96       }
97       return true;
98     }
99
100     PsiFile file = element.getContainingFile();
101     VirtualFile virtualFile = null;
102     if (file != null) {
103       virtualFile = file.getViewProvider().getVirtualFile();
104     }
105     else if (element instanceof PsiFileSystemItem) {
106       virtualFile = ((PsiFileSystemItem)element).getVirtualFile();
107     }
108     if (file != null && file.isPhysical() && virtualFile.getFileSystem() instanceof NonPhysicalFileSystem) return true;
109
110     return virtualFile != null && myFileIndex.getValue().isInContent(virtualFile);
111   }
112
113   @Override
114   @TestOnly
115   public void setAssertOnFileLoadingFilter(@NotNull VirtualFileFilter filter, @NotNull Disposable parentDisposable) {
116     // Find something to ensure there's no changed files waiting to be processed in repository indices.
117     myAssertOnFileLoadingFilter = filter;
118     Disposer.register(parentDisposable, () -> myAssertOnFileLoadingFilter = VirtualFileFilter.NONE);
119   }
120
121   @Override
122   public boolean isAssertOnFileLoading(@NotNull VirtualFile file) {
123     return myAssertOnFileLoadingFilter.accept(file);
124   }
125
126   @Override
127   @NotNull
128   public Project getProject() {
129     return myProject;
130   }
131
132   @Override
133   @NotNull
134   public FileManager getFileManager() {
135     return myFileManager;
136   }
137
138   @Override
139   public boolean areElementsEquivalent(PsiElement element1, PsiElement element2) {
140     ProgressIndicatorProvider.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly
141
142     if (element1 == element2) return true;
143     if (element1 == null || element2 == null) {
144       return false;
145     }
146
147     return element1.equals(element2) || element1.isEquivalentTo(element2) || element2.isEquivalentTo(element1);
148   }
149
150   @Override
151   public PsiFile findFile(@NotNull VirtualFile file) {
152     ProgressIndicatorProvider.checkCanceled();
153     return myFileManager.findFile(file);
154   }
155
156   @NotNull
157   @Override
158   public FileViewProvider findViewProvider(@NotNull VirtualFile file) {
159     ProgressIndicatorProvider.checkCanceled();
160     return myFileManager.findViewProvider(file);
161   }
162
163   @Override
164   public PsiDirectory findDirectory(@NotNull VirtualFile file) {
165     ProgressIndicatorProvider.checkCanceled();
166     return myFileManager.findDirectory(file);
167   }
168
169   @Override
170   public void reloadFromDisk(@NotNull PsiFile file) {
171     myFileManager.reloadFromDisk(file);
172   }
173
174   @Override
175   public void addPsiTreeChangeListener(@NotNull PsiTreeChangeListener listener) {
176     myTreeChangeListeners.add(listener);
177   }
178
179   @Override
180   public void addPsiTreeChangeListener(@NotNull final PsiTreeChangeListener listener, @NotNull Disposable parentDisposable) {
181     addPsiTreeChangeListener(listener);
182     Disposer.register(parentDisposable, () -> removePsiTreeChangeListener(listener));
183   }
184
185   @Override
186   public void removePsiTreeChangeListener(@NotNull PsiTreeChangeListener listener) {
187     myTreeChangeListeners.remove(listener);
188   }
189
190   private static String logPsi(@Nullable PsiElement element) {
191     return element == null ? " null" : element.getClass().getName();
192   }
193
194   @Override
195   public void beforeChildAddition(@NotNull PsiTreeChangeEventImpl event) {
196     beforeChange(true);
197     event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_ADDITION);
198     if (LOG.isDebugEnabled()) {
199       LOG.debug("beforeChildAddition: event = " + event);
200     }
201     fireEvent(event);
202   }
203
204   @Override
205   public void beforeChildRemoval(@NotNull PsiTreeChangeEventImpl event) {
206     beforeChange(true);
207     event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_REMOVAL);
208     if (LOG.isDebugEnabled()) {
209       LOG.debug("beforeChildRemoval: child = " + logPsi(event.getChild()) + ", parent = " + logPsi(event.getParent()));
210     }
211     fireEvent(event);
212   }
213
214   @Override
215   public void beforeChildReplacement(@NotNull PsiTreeChangeEventImpl event) {
216     beforeChange(true);
217     event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_REPLACEMENT);
218     if (LOG.isDebugEnabled()) {
219       LOG.debug("beforeChildReplacement: oldChild = " + logPsi(event.getOldChild()));
220     }
221     fireEvent(event);
222   }
223
224   public void beforeChildrenChange(@NotNull PsiTreeChangeEventImpl event) {
225     beforeChange(true);
226     event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILDREN_CHANGE);
227     if (LOG.isDebugEnabled()) {
228       LOG.debug("beforeChildrenChange: parent = " + logPsi(event.getParent()));
229     }
230     fireEvent(event);
231   }
232
233   public void beforeChildMovement(@NotNull PsiTreeChangeEventImpl event) {
234     beforeChange(true);
235     event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_MOVEMENT);
236     if (LOG.isDebugEnabled()) {
237       LOG.debug("beforeChildMovement: child = " + logPsi(event.getChild()) + ", oldParent = " + logPsi(event.getOldParent()) + ", newParent = " + logPsi(event.getNewParent()));
238     }
239     fireEvent(event);
240   }
241
242   public void beforePropertyChange(@NotNull PsiTreeChangeEventImpl event) {
243     beforeChange(true);
244     event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_PROPERTY_CHANGE);
245     if (LOG.isDebugEnabled()) {
246       LOG.debug("beforePropertyChange: element = " + logPsi(event.getElement()) + ", propertyName = " + event.getPropertyName() + ", oldValue = " +
247                 arrayToString(event.getOldValue()));
248     }
249     fireEvent(event);
250   }
251
252   private static Object arrayToString(Object value) {
253     return value instanceof Object[] ? Arrays.deepToString((Object[])value) : value;
254   }
255
256   public void childAdded(@NotNull PsiTreeChangeEventImpl event) {
257     event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_ADDED);
258     if (LOG.isDebugEnabled()) {
259       LOG.debug("childAdded: child = " + logPsi(event.getChild()) + ", parent = " + logPsi(event.getParent()));
260     }
261     fireEvent(event);
262     afterChange(true);
263   }
264
265   public void childRemoved(@NotNull PsiTreeChangeEventImpl event) {
266     event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_REMOVED);
267     if (LOG.isDebugEnabled()) {
268       LOG.debug("childRemoved: child = " + logPsi(event.getChild()) + ", parent = " + logPsi(event.getParent()));
269     }
270     fireEvent(event);
271     afterChange(true);
272   }
273
274   public void childReplaced(@NotNull PsiTreeChangeEventImpl event) {
275     event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_REPLACED);
276     if (LOG.isDebugEnabled()) {
277       LOG.debug("childReplaced: oldChild = " + logPsi(event.getOldChild()) + ", newChild = " + logPsi(event.getNewChild()) + ", parent = " + logPsi(event.getParent()));
278     }
279     fireEvent(event);
280     afterChange(true);
281   }
282
283   public void childMoved(@NotNull PsiTreeChangeEventImpl event) {
284     event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED);
285     if (LOG.isDebugEnabled()) {
286       LOG.debug("childMoved: child = " + logPsi(event.getChild()) + ", oldParent = " + logPsi(event.getOldParent()) + ", newParent = " + logPsi(event.getNewParent()));
287     }
288     fireEvent(event);
289     afterChange(true);
290   }
291
292   public void childrenChanged(@NotNull PsiTreeChangeEventImpl event) {
293     event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED);
294     if (LOG.isDebugEnabled()) {
295       LOG.debug("childrenChanged: parent = " + logPsi(event.getParent()));
296     }
297     fireEvent(event);
298     afterChange(true);
299   }
300
301   public void propertyChanged(@NotNull PsiTreeChangeEventImpl event) {
302     event.setCode(PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGED);
303     if (LOG.isDebugEnabled()) {
304       LOG.debug(
305         "propertyChanged: element = " + logPsi(event.getElement())
306         + ", propertyName = " + event.getPropertyName()
307         + ", oldValue = " + arrayToString(event.getOldValue())
308         + ", newValue = " + arrayToString(event.getNewValue())
309       );
310     }
311     fireEvent(event);
312     afterChange(true);
313   }
314
315   public void addTreeChangePreprocessor(@NotNull PsiTreeChangePreprocessor preprocessor) {
316     myTreeChangePreprocessors.add(preprocessor);
317   }
318
319   public void removeTreeChangePreprocessor(@NotNull PsiTreeChangePreprocessor preprocessor) {
320     myTreeChangePreprocessors.remove(preprocessor);
321   }
322
323   private void fireEvent(@NotNull PsiTreeChangeEventImpl event) {
324     boolean isRealTreeChange = event.getCode() != PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGED
325                                && event.getCode() != PsiTreeChangeEventImpl.PsiEventType.BEFORE_PROPERTY_CHANGE;
326
327     PsiFile file = event.getFile();
328     if (file == null || file.isPhysical()) {
329       ApplicationManager.getApplication().assertWriteAccessAllowed();
330     }
331     if (isRealTreeChange) {
332       LOG.assertTrue(!myTreeChangeEventIsFiring, "Changes to PSI are not allowed inside event processing");
333       myTreeChangeEventIsFiring = true;
334     }
335     try {
336       for (PsiTreeChangePreprocessor preprocessor : myTreeChangePreprocessors) {
337         preprocessor.treeChanged(event);
338       }
339       for (PsiTreeChangePreprocessor preprocessor : PsiTreeChangePreprocessor.EP.getExtensions(myProject)) {
340         try {
341           preprocessor.treeChanged(event);
342         }
343         catch (Throwable e) {
344           LOG.error(e);
345         }
346       }
347       for (PsiTreeChangeListener listener : myTreeChangeListeners) {
348         try {
349           switch (event.getCode()) {
350             case BEFORE_CHILD_ADDITION:
351               listener.beforeChildAddition(event);
352               break;
353
354             case BEFORE_CHILD_REMOVAL:
355               listener.beforeChildRemoval(event);
356               break;
357
358             case BEFORE_CHILD_REPLACEMENT:
359               listener.beforeChildReplacement(event);
360               break;
361
362             case BEFORE_CHILD_MOVEMENT:
363               listener.beforeChildMovement(event);
364               break;
365
366             case BEFORE_CHILDREN_CHANGE:
367               listener.beforeChildrenChange(event);
368               break;
369
370             case BEFORE_PROPERTY_CHANGE:
371               listener.beforePropertyChange(event);
372               break;
373
374             case CHILD_ADDED:
375               listener.childAdded(event);
376               break;
377
378             case CHILD_REMOVED:
379               listener.childRemoved(event);
380               break;
381
382             case CHILD_REPLACED:
383               listener.childReplaced(event);
384               break;
385
386             case CHILD_MOVED:
387               listener.childMoved(event);
388               break;
389
390             case CHILDREN_CHANGED:
391               listener.childrenChanged(event);
392               break;
393
394             case PROPERTY_CHANGED:
395               listener.propertyChanged(event);
396               break;
397           }
398         }
399         catch (Throwable e) {
400           LOG.error(e);
401         }
402       }
403     }
404     finally {
405       if (isRealTreeChange) {
406         myTreeChangeEventIsFiring = false;
407       }
408     }
409   }
410
411   @Override
412   public void registerRunnableToRunOnChange(@NotNull final Runnable runnable) {
413     myProject.getMessageBus().connect().subscribe(ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener.Adapter() {
414       @Override
415       public void beforePsiChanged(boolean isPhysical) {
416         if (isPhysical) runnable.run();
417       }
418     });
419   }
420
421   @Override
422   public void registerRunnableToRunOnAnyChange(@NotNull final Runnable runnable) { // includes non-physical changes
423     myProject.getMessageBus().connect().subscribe(ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener.Adapter() {
424       @Override
425       public void beforePsiChanged(boolean isPhysical) {
426         runnable.run();
427       }
428     });
429   }
430
431   @Override
432   public void registerRunnableToRunAfterAnyChange(@NotNull final Runnable runnable) { // includes non-physical changes
433     myProject.getMessageBus().connect().subscribe(ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener.Adapter() {
434       @Override
435       public void afterPsiChanged(boolean isPhysical) {
436         runnable.run();
437       }
438     });
439   }
440
441   @Override
442   public void beforeChange(boolean isPhysical) {
443     myProject.getMessageBus().syncPublisher(ANY_PSI_CHANGE_TOPIC).beforePsiChanged(isPhysical);
444   }
445
446   @Override
447   public void afterChange(boolean isPhysical) {
448     myProject.getMessageBus().syncPublisher(ANY_PSI_CHANGE_TOPIC).afterPsiChanged(isPhysical);
449   }
450
451   @Override
452   @NotNull
453   public PsiModificationTracker getModificationTracker() {
454     return myModificationTracker;
455   }
456
457   @Override
458   public void startBatchFilesProcessingMode() {
459     myBatchFilesProcessingModeCount.incrementAndGet();
460   }
461
462   @Override
463   public void finishBatchFilesProcessingMode() {
464     myBatchFilesProcessingModeCount.decrementAndGet();
465     LOG.assertTrue(myBatchFilesProcessingModeCount.get() >= 0);
466   }
467
468   @Override
469   public boolean isBatchFilesProcessingMode() {
470     return myBatchFilesProcessingModeCount.get() > 0;
471   }
472
473   @TestOnly
474   public void cleanupForNextTest() {
475     assert ApplicationManager.getApplication().isUnitTestMode();
476     myFileManager.cleanupForNextTest();
477     dropPsiCaches();
478   }
479
480   public void dropResolveCacheRegularly(@NotNull ProgressIndicator indicator) {
481     indicator = ProgressWrapper.unwrap(indicator);
482     if (indicator instanceof ProgressIndicatorEx) {
483       ((ProgressIndicatorEx)indicator).addStateDelegate(new AbstractProgressIndicatorExBase() {
484         private final AtomicLong lastClearedTimeStamp = new AtomicLong();
485
486         @Override
487         public void setFraction(double fraction) {
488           long current = System.currentTimeMillis();
489           long last = lastClearedTimeStamp.get();
490           if (current - last >= 500 && lastClearedTimeStamp.compareAndSet(last, current)) {
491             // fraction is changed when each file is processed =>
492             // resolve caches used when searching in that file are likely to be not needed anymore
493             dropResolveCaches();
494           }
495         }
496       });
497     }
498   }
499 }