cleanup
[idea/community.git] / platform / core-impl / src / com / intellij / psi / impl / PsiManagerImpl.java
1 // Copyright 2000-2020 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 = new Topic<>(AnyPsiChangeListener.class, Topic.BroadcastDirection.TO_PARENT);
54
55   public PsiManagerImpl(@NotNull Project project) {
56     // we need to initialize PsiBuilderFactory service so it won't initialize under PsiLock from ChameleonTransform
57     PsiBuilderFactory.getInstance();
58
59     myProject = project;
60     myFileIndex = NotNullLazyValue.createValue(() -> FileIndexFacade.getInstance(project));
61     myModificationTracker = PsiModificationTracker.SERVICE.getInstance(project);
62
63     myFileManager = new FileManagerImpl(this, myFileIndex);
64
65     myTreeChangePreprocessors.add((PsiTreeChangePreprocessor)myModificationTracker);
66   }
67
68   @Override
69   public void dispose() {
70     myFileManager.dispose();
71   }
72
73   @Override
74   public boolean isDisposed() {
75     return myProject.isDisposed();
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         notifyPsiTreeChangeListener(event, listener);
349       }
350       for (PsiTreeChangeListener listener : PsiTreeChangeListener.EP.getExtensions(myProject)) {
351         notifyPsiTreeChangeListener(event, listener);
352       }
353     }
354     finally {
355       if (isRealTreeChange) {
356         myTreeChangeEventIsFiring = false;
357       }
358     }
359   }
360
361   private static void notifyPsiTreeChangeListener(@NotNull PsiTreeChangeEventImpl event, PsiTreeChangeListener listener) {
362     try {
363       switch (event.getCode()) {
364         case BEFORE_CHILD_ADDITION:
365           listener.beforeChildAddition(event);
366           break;
367
368         case BEFORE_CHILD_REMOVAL:
369           listener.beforeChildRemoval(event);
370           break;
371
372         case BEFORE_CHILD_REPLACEMENT:
373           listener.beforeChildReplacement(event);
374           break;
375
376         case BEFORE_CHILD_MOVEMENT:
377           listener.beforeChildMovement(event);
378           break;
379
380         case BEFORE_CHILDREN_CHANGE:
381           listener.beforeChildrenChange(event);
382           break;
383
384         case BEFORE_PROPERTY_CHANGE:
385           listener.beforePropertyChange(event);
386           break;
387
388         case CHILD_ADDED:
389           listener.childAdded(event);
390           break;
391
392         case CHILD_REMOVED:
393           listener.childRemoved(event);
394           break;
395
396         case CHILD_REPLACED:
397           listener.childReplaced(event);
398           break;
399
400         case CHILD_MOVED:
401           listener.childMoved(event);
402           break;
403
404         case CHILDREN_CHANGED:
405           listener.childrenChanged(event);
406           break;
407
408         case PROPERTY_CHANGED:
409           listener.propertyChanged(event);
410           break;
411       }
412     }
413     catch (Throwable e) {
414       LOG.error(e);
415     }
416   }
417
418   @Override
419   public void registerRunnableToRunOnChange(@NotNull final Runnable runnable) {
420     myProject.getMessageBus().connect().subscribe(ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener() {
421       @Override
422       public void beforePsiChanged(boolean isPhysical) {
423         if (isPhysical) runnable.run();
424       }
425     });
426   }
427
428   @Override
429   public void registerRunnableToRunOnAnyChange(@NotNull final Runnable runnable) { // includes non-physical changes
430     myProject.getMessageBus().connect().subscribe(ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener() {
431       @Override
432       public void beforePsiChanged(boolean isPhysical) {
433         runnable.run();
434       }
435     });
436   }
437
438   @Override
439   public void registerRunnableToRunAfterAnyChange(@NotNull final Runnable runnable) { // includes non-physical changes
440     myProject.getMessageBus().connect().subscribe(ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener() {
441       @Override
442       public void afterPsiChanged(boolean isPhysical) {
443         runnable.run();
444       }
445     });
446   }
447
448   @Override
449   public void beforeChange(boolean isPhysical) {
450     myProject.getMessageBus().syncPublisher(ANY_PSI_CHANGE_TOPIC).beforePsiChanged(isPhysical);
451   }
452
453   @Override
454   public void afterChange(boolean isPhysical) {
455     myProject.getMessageBus().syncPublisher(ANY_PSI_CHANGE_TOPIC).afterPsiChanged(isPhysical);
456   }
457
458   @Override
459   @NotNull
460   public PsiModificationTracker getModificationTracker() {
461     return myModificationTracker;
462   }
463
464   @Override
465   public void startBatchFilesProcessingMode() {
466     myBatchFilesProcessingModeCount.incrementAndGet();
467   }
468
469   @Override
470   public void finishBatchFilesProcessingMode() {
471     int after = myBatchFilesProcessingModeCount.decrementAndGet();
472     LOG.assertTrue(after >= 0);
473   }
474
475   @Override
476   public boolean isBatchFilesProcessingMode() {
477     return myBatchFilesProcessingModeCount.get() > 0;
478   }
479
480   @TestOnly
481   public void cleanupForNextTest() {
482     assert ApplicationManager.getApplication().isUnitTestMode();
483     myFileManager.cleanupForNextTest();
484     dropPsiCaches();
485   }
486
487   public void dropResolveCacheRegularly(@NotNull ProgressIndicator indicator) {
488     indicator = ProgressWrapper.unwrap(indicator);
489     if (indicator instanceof ProgressIndicatorEx) {
490       ((ProgressIndicatorEx)indicator).addStateDelegate(new AbstractProgressIndicatorExBase() {
491         private final AtomicLong lastClearedTimeStamp = new AtomicLong();
492
493         @Override
494         public void setFraction(double fraction) {
495           long current = System.currentTimeMillis();
496           long last = lastClearedTimeStamp.get();
497           if (current - last >= 500 && lastClearedTimeStamp.compareAndSet(last, current)) {
498             // fraction is changed when each file is processed =>
499             // resolve caches used when searching in that file are likely to be not needed anymore
500             dropResolveCaches();
501           }
502         }
503       });
504     }
505   }
506 }