package jetbrains.mps.idea.java.refactoring; import com.intellij.openapi.command.CommandAdapter; import com.intellij.openapi.command.CommandEvent; import com.intellij.openapi.command.CommandListener; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.undo.DocumentReference; import com.intellij.openapi.command.undo.UndoManager; import com.intellij.openapi.command.undo.UndoableAction; import com.intellij.openapi.command.undo.UnexpectedUndoException; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.project.Project; import jetbrains.mps.ide.platform.watching.ReloadManager; import jetbrains.mps.ide.project.ProjectHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SNodeReference; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * This component is mps-java internal. Not supposed to be used from other plugins. * Java specific ancestors of MPSPsiRef use it to schedule their reference update code * so that it is invoked as a batch at the end of command. * Also IdPrefixReference decides whether it should update its reference or not based on * whether the appropriate node is already handled by 'normal' reference (SReference). * <p/> * danilla 3/19/13 */ public class MoveRenameBatch implements ProjectComponent { private Project myProject; private Object myCommand; private boolean isUndoRedoCommand; private CommandListener myCommandListener; private UndoableAction myUndoRedoSupport; // generic updaters, not tied to references, used at least for updating method names private List<Runnable> nodeUpdaters = new ArrayList<Runnable>(); // reference -> update code private Map<SReferencePtr, Runnable> sreferenceUpdaters = new HashMap<SReferencePtr, Runnable>(); private Map<SReferencePtr, Runnable> prefixReferenceUpdaters = new HashMap<SReferencePtr, Runnable>(); public MoveRenameBatch(Project project) { myProject = project; } @Override public void projectOpened() { } @Override public void projectClosed() { } @Override public void initComponent() { myCommandListener = new CommandAdapter() { // Refactoring is over (if this command was a refactoring at all) @Override public void beforeCommandFinished(CommandEvent event) { if (event.getCommand() == myCommand) { if (!isUndoRedoCommand) { // our refactoring command is over commit(); } else { // refactoring undo or redo command is over // the effect of undo/redo of commit() is already done automatically // we just want our psi stub models to be in sync with psi ReloadManager.getInstance().flush(); } } myCommand = null; isUndoRedoCommand = false; nodeUpdaters.clear(); sreferenceUpdaters.clear(); prefixReferenceUpdaters.clear(); } }; myUndoRedoSupport = new UndoableAction() { @Override public void undo() throws UnexpectedUndoException { rememberCommand(); isUndoRedoCommand = true; } @Override public void redo() throws UnexpectedUndoException { rememberCommand(); isUndoRedoCommand = true; } @Nullable @Override public DocumentReference[] getAffectedDocuments() { return DocumentReference.EMPTY_ARRAY; } @Override public boolean isGlobal() { return false; } }; CommandProcessor.getInstance().addCommandListener(myCommandListener); } @Override public void disposeComponent() { myCommand = null; sreferenceUpdaters.clear(); prefixReferenceUpdaters.clear(); } @NotNull @Override public String getComponentName() { return "Batch for move/rename refactoring"; } // ---- private void rememberCommand() { // replace asserts with exceptions? Object currCommand = CommandProcessor.getInstance().getCurrentCommand(); // we must only be called inside command assert currCommand != null; if (myCommand == null) { myCommand = currCommand; } // and only one single command for one batch assert currCommand == myCommand; } public void commit() { // TODO !!! a better way to synchronize idea and mps is needed // when refactoring starts we have to stop reloads // and here resume and flush ReloadManager.getInstance().flush(); UndoManager.getInstance(myProject).undoableActionPerformed(myUndoRedoSupport); ProjectHelper.getModelAccess(myProject).executeUndoTransparentCommand(new Runnable() { @Override public void run() { for (Runnable upd : nodeUpdaters) { upd.run(); } for (Runnable upd : sreferenceUpdaters.values()) { upd.run(); } for (SReferencePtr link : prefixReferenceUpdaters.keySet()) { if (!sreferenceUpdaters.containsKey(link)) { // this reference isn't handled by a normal SReference updater // (in the current MoveRanameBatch, i.e. during current refactoring command) prefixReferenceUpdaters.get(link).run(); } } } }); } public void scheduleNodeUpdate(Runnable r) { nodeUpdaters.add(r); } public void scheduleNormalRefUpdate(SNodeReference source, String role, Runnable r) { rememberCommand(); sreferenceUpdaters.put(new SReferencePtr(source, role), r); } public void scheduleIdPrefixRefUpdate(SNodeReference source, String role, Runnable r) { rememberCommand(); prefixReferenceUpdaters.put(new SReferencePtr(source, role), r); } }