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