| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.psi.impl; |
| |
| import com.intellij.lang.PsiBuilderFactory; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.progress.ProgressIndicatorProvider; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.FileIndexFacade; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.VirtualFileFilter; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.file.impl.FileManager; |
| import com.intellij.psi.impl.file.impl.FileManagerImpl; |
| import com.intellij.psi.util.PsiModificationTracker; |
| import com.intellij.testFramework.LightVirtualFile; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.messages.MessageBus; |
| import com.intellij.util.messages.Topic; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| public class PsiManagerImpl extends PsiManagerEx { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiManagerImpl"); |
| |
| private final Project myProject; |
| private final FileIndexFacade myExcludedFileIndex; |
| private final MessageBus myMessageBus; |
| private final PsiModificationTracker myModificationTracker; |
| |
| private final FileManager myFileManager; |
| |
| private final List<PsiTreeChangePreprocessor> myTreeChangePreprocessors = ContainerUtil.createLockFreeCopyOnWriteList(); |
| private final List<PsiTreeChangeListener> myTreeChangeListeners = ContainerUtil.createLockFreeCopyOnWriteList(); |
| private boolean myTreeChangeEventIsFiring = false; |
| |
| private boolean myIsDisposed; |
| |
| private VirtualFileFilter myAssertOnFileLoadingFilter = VirtualFileFilter.NONE; |
| |
| private final AtomicInteger myBatchFilesProcessingModeCount = new AtomicInteger(0); |
| |
| public static final Topic<AnyPsiChangeListener> ANY_PSI_CHANGE_TOPIC = |
| Topic.create("ANY_PSI_CHANGE_TOPIC", AnyPsiChangeListener.class, Topic.BroadcastDirection.TO_PARENT); |
| |
| public PsiManagerImpl(Project project, |
| FileDocumentManager fileDocumentManager, |
| PsiBuilderFactory psiBuilderFactory, |
| FileIndexFacade excludedFileIndex, |
| MessageBus messageBus, |
| PsiModificationTracker modificationTracker) { |
| myProject = project; |
| myExcludedFileIndex = excludedFileIndex; |
| myMessageBus = messageBus; |
| myModificationTracker = modificationTracker; |
| |
| //We need to initialize PsiBuilderFactory service so it won't initialize under PsiLock from ChameleonTransform |
| @SuppressWarnings({"UnusedDeclaration", "UnnecessaryLocalVariable"}) Object used = psiBuilderFactory; |
| |
| boolean isProjectDefault = project.isDefault(); |
| |
| myFileManager = isProjectDefault ? new EmptyFileManager(this) : new FileManagerImpl(this, fileDocumentManager, excludedFileIndex); |
| |
| myTreeChangePreprocessors.add((PsiTreeChangePreprocessor)modificationTracker); |
| Collections.addAll(myTreeChangePreprocessors, Extensions.getExtensions(PsiTreeChangePreprocessor.EP_NAME, myProject)); |
| |
| Disposer.register(project, new Disposable() { |
| @Override |
| public void dispose() { |
| myIsDisposed = true; |
| } |
| }); |
| } |
| |
| @Override |
| public boolean isDisposed() { |
| return myIsDisposed; |
| } |
| |
| @Override |
| public void dropResolveCaches() { |
| FileManager fileManager = myFileManager; |
| if (fileManager instanceof FileManagerImpl) { // mock tests |
| ((FileManagerImpl)fileManager).processQueue(); |
| } |
| beforeChange(true); |
| beforeChange(false); |
| } |
| |
| @Override |
| public boolean isInProject(@NotNull PsiElement element) { |
| PsiFile file = element.getContainingFile(); |
| if (file != null && file.isPhysical() && file.getViewProvider().getVirtualFile() instanceof LightVirtualFile) return true; |
| |
| if (element instanceof PsiDirectoryContainer) { |
| PsiDirectory[] dirs = ((PsiDirectoryContainer)element).getDirectories(); |
| for (PsiDirectory dir : dirs) { |
| if (!isInProject(dir)) return false; |
| } |
| return true; |
| } |
| |
| VirtualFile virtualFile = null; |
| if (file != null) { |
| virtualFile = file.getViewProvider().getVirtualFile(); |
| } |
| else if (element instanceof PsiFileSystemItem) { |
| virtualFile = ((PsiFileSystemItem)element).getVirtualFile(); |
| } |
| |
| if (virtualFile != null) { |
| return myExcludedFileIndex.isInContent(virtualFile); |
| } |
| return false; |
| } |
| |
| @TestOnly |
| public void setAssertOnFileLoadingFilter(@NotNull VirtualFileFilter filter, @NotNull Disposable parentDisposable) { |
| // Find something to ensure there's no changed files waiting to be processed in repository indices. |
| myAssertOnFileLoadingFilter = filter; |
| Disposer.register(parentDisposable, new Disposable() { |
| @Override |
| public void dispose() { |
| myAssertOnFileLoadingFilter = VirtualFileFilter.NONE; |
| } |
| }); |
| } |
| |
| @Override |
| public boolean isAssertOnFileLoading(@NotNull VirtualFile file) { |
| return myAssertOnFileLoadingFilter.accept(file); |
| } |
| |
| @Override |
| @NotNull |
| public Project getProject() { |
| return myProject; |
| } |
| |
| @Override |
| @NotNull |
| public FileManager getFileManager() { |
| return myFileManager; |
| } |
| |
| @Override |
| public boolean areElementsEquivalent(PsiElement element1, PsiElement element2) { |
| ProgressIndicatorProvider.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly |
| |
| if (element1 == element2) return true; |
| if (element1 == null || element2 == null) { |
| return false; |
| } |
| |
| return element1.equals(element2) || element1.isEquivalentTo(element2) || element2.isEquivalentTo(element1); |
| } |
| |
| @Override |
| public PsiFile findFile(@NotNull VirtualFile file) { |
| return myFileManager.findFile(file); |
| } |
| |
| @Override |
| @Nullable |
| public FileViewProvider findViewProvider(@NotNull VirtualFile file) { |
| return myFileManager.findViewProvider(file); |
| } |
| |
| @TestOnly |
| public void cleanupForNextTest() { |
| myFileManager.cleanupForNextTest(); |
| LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode()); |
| } |
| |
| @Override |
| public PsiDirectory findDirectory(@NotNull VirtualFile file) { |
| ProgressIndicatorProvider.checkCanceled(); |
| |
| return myFileManager.findDirectory(file); |
| } |
| |
| @Override |
| public void reloadFromDisk(@NotNull PsiFile file) { |
| myFileManager.reloadFromDisk(file); |
| } |
| |
| @Override |
| public void addPsiTreeChangeListener(@NotNull PsiTreeChangeListener listener) { |
| myTreeChangeListeners.add(listener); |
| } |
| |
| @Override |
| public void addPsiTreeChangeListener(@NotNull final PsiTreeChangeListener listener, @NotNull Disposable parentDisposable) { |
| addPsiTreeChangeListener(listener); |
| Disposer.register(parentDisposable, new Disposable() { |
| @Override |
| public void dispose() { |
| removePsiTreeChangeListener(listener); |
| } |
| }); |
| } |
| |
| @Override |
| public void removePsiTreeChangeListener(@NotNull PsiTreeChangeListener listener) { |
| myTreeChangeListeners.remove(listener); |
| } |
| |
| @Override |
| public void beforeChildAddition(@NotNull PsiTreeChangeEventImpl event) { |
| beforeChange(true); |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_ADDITION); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "beforeChildAddition: parent = " + event.getParent() |
| ); |
| } |
| fireEvent(event); |
| } |
| |
| @Override |
| public void beforeChildRemoval(@NotNull PsiTreeChangeEventImpl event) { |
| beforeChange(true); |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_REMOVAL); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "beforeChildRemoval: child = " + event.getChild() |
| + ", parent = " + event.getParent() |
| ); |
| } |
| fireEvent(event); |
| } |
| |
| @Override |
| public void beforeChildReplacement(@NotNull PsiTreeChangeEventImpl event) { |
| beforeChange(true); |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_REPLACEMENT); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "beforeChildReplacement: oldChild = " + event.getOldChild() |
| + ", parent = " + event.getParent() |
| ); |
| } |
| fireEvent(event); |
| } |
| |
| public void beforeChildrenChange(@NotNull PsiTreeChangeEventImpl event) { |
| beforeChange(true); |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILDREN_CHANGE); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("beforeChildrenChange: parent = " + event.getParent()); |
| } |
| fireEvent(event); |
| } |
| |
| public void beforeChildMovement(@NotNull PsiTreeChangeEventImpl event) { |
| beforeChange(true); |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_CHILD_MOVEMENT); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "beforeChildMovement: child = " + event.getChild() |
| + ", oldParent = " + event.getOldParent() |
| + ", newParent = " + event.getNewParent() |
| ); |
| } |
| fireEvent(event); |
| } |
| |
| public void beforePropertyChange(@NotNull PsiTreeChangeEventImpl event) { |
| beforeChange(true); |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.BEFORE_PROPERTY_CHANGE); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "beforePropertyChange: element = " + event.getElement() |
| + ", propertyName = " + event.getPropertyName() |
| + ", oldValue = " + event.getOldValue() |
| ); |
| } |
| fireEvent(event); |
| } |
| |
| public void childAdded(@NotNull PsiTreeChangeEventImpl event) { |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_ADDED); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "childAdded: child = " + event.getChild() |
| + ", parent = " + event.getParent() |
| ); |
| } |
| fireEvent(event); |
| afterChange(true); |
| } |
| |
| public void childRemoved(@NotNull PsiTreeChangeEventImpl event) { |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_REMOVED); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "childRemoved: child = " + event.getChild() + ", parent = " + event.getParent() |
| ); |
| } |
| fireEvent(event); |
| afterChange(true); |
| } |
| |
| public void childReplaced(@NotNull PsiTreeChangeEventImpl event) { |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_REPLACED); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "childReplaced: oldChild = " + event.getOldChild() |
| + ", newChild = " + event.getNewChild() |
| + ", parent = " + event.getParent() |
| ); |
| } |
| fireEvent(event); |
| afterChange(true); |
| } |
| |
| public void childMoved(@NotNull PsiTreeChangeEventImpl event) { |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILD_MOVED); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "childMoved: child = " + event.getChild() |
| + ", oldParent = " + event.getOldParent() |
| + ", newParent = " + event.getNewParent() |
| ); |
| } |
| fireEvent(event); |
| afterChange(true); |
| } |
| |
| public void childrenChanged(@NotNull PsiTreeChangeEventImpl event) { |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "childrenChanged: parent = " + event.getParent() |
| ); |
| } |
| fireEvent(event); |
| afterChange(true); |
| } |
| |
| public void propertyChanged(@NotNull PsiTreeChangeEventImpl event) { |
| event.setCode(PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGED); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug( |
| "propertyChanged: element = " + event.getElement() |
| + ", propertyName = " + event.getPropertyName() |
| + ", oldValue = " + event.getOldValue() |
| + ", newValue = " + event.getNewValue() |
| ); |
| } |
| fireEvent(event); |
| afterChange(true); |
| } |
| |
| public void addTreeChangePreprocessor(@NotNull PsiTreeChangePreprocessor preprocessor) { |
| myTreeChangePreprocessors.add(preprocessor); |
| } |
| |
| private void fireEvent(@NotNull PsiTreeChangeEventImpl event) { |
| boolean isRealTreeChange = event.getCode() != PsiTreeChangeEventImpl.PsiEventType.PROPERTY_CHANGED |
| && event.getCode() != PsiTreeChangeEventImpl.PsiEventType.BEFORE_PROPERTY_CHANGE; |
| |
| PsiFile file = event.getFile(); |
| if (file == null || file.isPhysical()) { |
| ApplicationManager.getApplication().assertWriteAccessAllowed(); |
| } |
| if (isRealTreeChange) { |
| LOG.assertTrue(!myTreeChangeEventIsFiring, "Changes to PSI are not allowed inside event processing"); |
| myTreeChangeEventIsFiring = true; |
| } |
| try { |
| for (PsiTreeChangePreprocessor preprocessor : myTreeChangePreprocessors) { |
| preprocessor.treeChanged(event); |
| } |
| |
| for (PsiTreeChangeListener listener : myTreeChangeListeners) { |
| try { |
| switch (event.getCode()) { |
| case BEFORE_CHILD_ADDITION: |
| listener.beforeChildAddition(event); |
| break; |
| |
| case BEFORE_CHILD_REMOVAL: |
| listener.beforeChildRemoval(event); |
| break; |
| |
| case BEFORE_CHILD_REPLACEMENT: |
| listener.beforeChildReplacement(event); |
| break; |
| |
| case BEFORE_CHILD_MOVEMENT: |
| listener.beforeChildMovement(event); |
| break; |
| |
| case BEFORE_CHILDREN_CHANGE: |
| listener.beforeChildrenChange(event); |
| break; |
| |
| case BEFORE_PROPERTY_CHANGE: |
| listener.beforePropertyChange(event); |
| break; |
| |
| case CHILD_ADDED: |
| listener.childAdded(event); |
| break; |
| |
| case CHILD_REMOVED: |
| listener.childRemoved(event); |
| break; |
| |
| case CHILD_REPLACED: |
| listener.childReplaced(event); |
| break; |
| |
| case CHILD_MOVED: |
| listener.childMoved(event); |
| break; |
| |
| case CHILDREN_CHANGED: |
| listener.childrenChanged(event); |
| break; |
| |
| case PROPERTY_CHANGED: |
| listener.propertyChanged(event); |
| break; |
| } |
| } |
| catch (Exception e) { |
| LOG.error(e); |
| } |
| } |
| } |
| finally { |
| if (isRealTreeChange) { |
| myTreeChangeEventIsFiring = false; |
| } |
| } |
| } |
| |
| @Override |
| public void registerRunnableToRunOnChange(@NotNull final Runnable runnable) { |
| myMessageBus.connect().subscribe(ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener() { |
| @Override |
| public void beforePsiChanged(boolean isPhysical) { |
| if (isPhysical) runnable.run(); |
| } |
| |
| @Override |
| public void afterPsiChanged(boolean isPhysical) { |
| } |
| }); |
| } |
| |
| @Override |
| public void registerRunnableToRunOnAnyChange(@NotNull final Runnable runnable) { // includes non-physical changes |
| myMessageBus.connect().subscribe(ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener() { |
| @Override |
| public void beforePsiChanged(boolean isPhysical) { |
| runnable.run(); |
| } |
| |
| @Override |
| public void afterPsiChanged(boolean isPhysical) { |
| } |
| }); |
| } |
| |
| @Override |
| public void registerRunnableToRunAfterAnyChange(@NotNull final Runnable runnable) { // includes non-physical changes |
| myMessageBus.connect().subscribe(ANY_PSI_CHANGE_TOPIC, new AnyPsiChangeListener() { |
| @Override |
| public void beforePsiChanged(boolean isPhysical) { |
| } |
| |
| @Override |
| public void afterPsiChanged(boolean isPhysical) { |
| runnable.run(); |
| } |
| }); |
| } |
| |
| @Override |
| public void beforeChange(boolean isPhysical) { |
| myMessageBus.syncPublisher(ANY_PSI_CHANGE_TOPIC).beforePsiChanged(isPhysical); |
| } |
| |
| @Override |
| public void afterChange(boolean isPhysical) { |
| myMessageBus.syncPublisher(ANY_PSI_CHANGE_TOPIC).afterPsiChanged(isPhysical); |
| } |
| |
| @Override |
| @NotNull |
| public PsiModificationTracker getModificationTracker() { |
| return myModificationTracker; |
| } |
| |
| @Override |
| public void startBatchFilesProcessingMode() { |
| myBatchFilesProcessingModeCount.incrementAndGet(); |
| } |
| |
| @Override |
| public void finishBatchFilesProcessingMode() { |
| myBatchFilesProcessingModeCount.decrementAndGet(); |
| LOG.assertTrue(myBatchFilesProcessingModeCount.get() >= 0); |
| } |
| |
| @Override |
| public boolean isBatchFilesProcessingMode() { |
| return myBatchFilesProcessingModeCount.get() > 0; |
| } |
| } |