/* * Copyright 2003-2012 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 jetbrains.mps.idea.java.psi; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.refactoring.rename.inplace.InplaceRefactoring; import com.intellij.util.messages.MessageBusConnection; import jetbrains.mps.ide.platform.watching.ReloadAction; import jetbrains.mps.ide.platform.watching.ReloadManagerComponent; import jetbrains.mps.idea.java.psi.JavaPsiListener.PsiEvent; import jetbrains.mps.idea.core.psi.impl.MPSPsiNodeBase; import org.jetbrains.annotations.NotNull; import java.util.*; /** * danilla 11/12/12 */ public class PsiChangesWatcher implements ProjectComponent { private final Object LOCK = new Object(); private Project myProject; private PsiManager myPsiManager; private Set<JavaPsiListener> myListeners = new HashSet<JavaPsiListener>(); // this is to allow listeners call code that adds/removes another listeners // e.g. modelRoot (listener) calls update() that attaches models, which in turn start to listen PSI on attach // note: it all happens in one same thread, so LOCK doesn't work as a guard private boolean isNotifying = false; private Set<JavaPsiListener> toBeAdded = new HashSet<JavaPsiListener>(); private Set<JavaPsiListener> toBeRemoved = new HashSet<JavaPsiListener>(); private MessageBusConnection connection; private PsiTreeChangeListener myOwnPsiListener = new OwnPsiListener(); private ReloadManagerComponent myReloadManager; PsiChangesWatcher(Project p, ReloadManagerComponent reloadManager) { myProject = p; myReloadManager = reloadManager; } @Override public void initComponent() { myPsiManager = PsiManager.getInstance(myProject); } @Override public void disposeComponent() { myListeners.clear(); } @NotNull @Override public String getComponentName() { return "JavaStubPsiChangesWatcher"; } @Override public void projectOpened() { // Q: check for listeners.notEmpty and MPS facet? myPsiManager.addPsiTreeChangeListener(myOwnPsiListener); } @Override public void projectClosed() { myPsiManager.removePsiTreeChangeListener(myOwnPsiListener); } public void addListener(JavaPsiListener listener) { synchronized (LOCK) { if (!isNotifying) { myListeners.add(listener); } else { toBeAdded.add(listener); } // in case it was previously removed during this notification loop toBeRemoved.remove(listener); } } public void removeListener(JavaPsiListener listener) { synchronized (LOCK) { if (!isNotifying) { myListeners.remove(listener); } else { toBeRemoved.add(listener); } // in case it was pending-added during this notification loop toBeAdded.remove(listener); } } // called by PsiChangeProcessor /* package */ void notifyListeners(PsiEvent event) { synchronized (LOCK) { try { isNotifying = true; for (JavaPsiListener l : myListeners) { l.psiChanged(event); } } finally { isNotifying = false; // handling pending additions/removals which have been added/removed as a result // of previous listener invocations if (!toBeAdded.isEmpty()) { myListeners.addAll(toBeAdded); } if (!toBeRemoved.isEmpty()) { myListeners.removeAll(toBeRemoved); } toBeAdded.clear(); toBeRemoved.clear(); } } } private class OwnPsiListener extends PsiTreeChangeAdapter { @Override public void childAdded(final PsiTreeChangeEvent event) { if (isFromMPSPsiProvider(event)) return; myReloadManager.runReload(PsiChangeProcessor.class, new ReloadAction<PsiChangeProcessor>() { @Override public void runAction(PsiChangeProcessor p) { p.childAdded(event); } }); } @Override public void childRemoved(final PsiTreeChangeEvent event) { if (isFromMPSPsiProvider(event)) return; myReloadManager.runReload(PsiChangeProcessor.class, new ReloadAction<PsiChangeProcessor>() { @Override public void runAction(PsiChangeProcessor p) { p.childRemoved(event); } }); } @Override public void childReplaced(final PsiTreeChangeEvent event) { if (isFromMPSPsiProvider(event)) return; Editor editor = FileEditorManager.getInstance(myProject).getSelectedTextEditor(); if (editor != null) { InplaceRefactoring ir = editor.getUserData(InplaceRefactoring.INPLACE_RENAMER); if (ir != null) { return; } } myReloadManager.runReload(PsiChangeProcessor.class, new ReloadAction<PsiChangeProcessor>() { @Override public void runAction(PsiChangeProcessor p) { p.childReplaced(event); } }); } @Override public void childrenChanged(final PsiTreeChangeEvent event) { if (isFromMPSPsiProvider(event)) return; myReloadManager.runReload(PsiChangeProcessor.class, new ReloadAction<PsiChangeProcessor>() { @Override public void runAction(PsiChangeProcessor p) { p.childrenChanged(event); } }); } @Override public void childMoved(@NotNull final PsiTreeChangeEvent event) { if (isFromMPSPsiProvider(event)) return; myReloadManager.runReload(PsiChangeProcessor.class, new ReloadAction<PsiChangeProcessor>() { @Override public void runAction(PsiChangeProcessor p) { p.childMoved(event); } }); } @Override public void propertyChanged(@NotNull final PsiTreeChangeEvent event) { if (isFromMPSPsiProvider(event)) return; myReloadManager.runReload(PsiChangeProcessor.class, new ReloadAction<PsiChangeProcessor>() { @Override public void runAction(PsiChangeProcessor p) { p.propertyChanged(event); } }); } private boolean isFromMPSPsiProvider(PsiTreeChangeEvent event) { PsiElement parent = event.getParent(); return (parent instanceof MPSPsiNodeBase); } } }