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);
}
}