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