blob: a67344e57222123fd44ee7bdf32350e8d0456e02 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ltk.internal.core.refactoring;
import java.util.Collection;
import java.util.Iterator;
import java.util.Stack;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.IUndoManager;
import org.eclipse.ltk.core.refactoring.IUndoManagerListener;
import org.eclipse.ltk.core.refactoring.IValidationCheckResultQuery;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
/**
* Default implementation of IUndoManager.
*/
public class UndoManager implements IUndoManager {
private Stack fUndoChanges;
private Stack fRedoChanges;
private Stack fUndoNames;
private Stack fRedoNames;
private ListenerList fListeners;
// Maximum numbers of undos on the refactoring undo stack.
private static final int MAX_UNDO_REDOS= 5;
private static class NullQuery implements IValidationCheckResultQuery {
public boolean proceed(RefactoringStatus status) {
return true;
}
public void stopped(RefactoringStatus status) {
// do nothing
}
}
/**
* Creates a new undo manager with an empty undo and redo stack.
*/
public UndoManager() {
flush();
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public void addListener(IUndoManagerListener listener) {
if (fListeners == null)
fListeners= new ListenerList(ListenerList.IDENTITY);
fListeners.add(listener);
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public void removeListener(IUndoManagerListener listener) {
if (fListeners == null)
return;
fListeners.remove(listener);
if (fListeners.size() == 0)
fListeners= null;
}
public void aboutToPerformChange(final Change change) {
if (fListeners == null)
return;
Object[] listeners= fListeners.getListeners();
for (int i= 0; i < listeners.length; i++) {
final IUndoManagerListener listener= (IUndoManagerListener)listeners[i];
SafeRunner.run(new ISafeRunnable() {
public void run() throws Exception {
listener.aboutToPerformChange(UndoManager.this, change);
}
public void handleException(Throwable exception) {
RefactoringCorePlugin.log(exception);
}
});
}
}
public void changePerformed(final Change change) {
if (fListeners == null)
return;
Object[] listeners= fListeners.getListeners();
for (int i= 0; i < listeners.length; i++) {
final IUndoManagerListener listener= (IUndoManagerListener)listeners[i];
SafeRunner.run(new ISafeRunnable() {
public void run() throws Exception {
listener.changePerformed(UndoManager.this, change);
}
public void handleException(Throwable exception) {
RefactoringCorePlugin.log(exception);
}
});
}
}
public void changePerformed(Change change, boolean successful) {
// the listeners don't care about success or not.
changePerformed(change);
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public void aboutToPerformRefactoring() {
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public void refactoringPerformed(boolean success) {
}
/*
* (non-Javadoc)
*
* @see IUndoManager#shutdown()
*/
public void shutdown() {
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public void flush() {
flushUndo();
flushRedo();
}
private void flushUndo() {
if (fUndoChanges != null) {
sendDispose(fUndoChanges);
}
fUndoChanges= new Stack();
fUndoNames= new Stack();
fireUndoStackChanged();
}
private void flushRedo() {
if (fRedoChanges != null) {
sendDispose(fRedoChanges);
}
fRedoChanges= new Stack();
fRedoNames= new Stack();
fireRedoStackChanged();
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public void addUndo(String refactoringName, Change change) {
Assert.isNotNull(refactoringName, "refactoring"); //$NON-NLS-1$
Assert.isNotNull(change, "change"); //$NON-NLS-1$
fUndoNames.push(refactoringName);
fUndoChanges.push(change);
if (fUndoChanges.size() > MAX_UNDO_REDOS) {
Change removedChange= (Change)fUndoChanges.remove(0);
fUndoNames.remove(0);
removedChange.dispose();
}
flushRedo();
fireUndoStackChanged();
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public void performUndo(IValidationCheckResultQuery query, IProgressMonitor pm) throws CoreException {
if (pm == null)
pm= new NullProgressMonitor();
RefactoringStatus result= new RefactoringStatus();
if (fUndoChanges.empty())
return;
Change change= (Change)fUndoChanges.pop();
if (query == null)
query= new NullQuery();
Change redo;
try {
redo= executeChange(result, change, query, pm);
} catch (InterruptedException e) {
fUndoChanges.push(change);
return;
}
if (!result.hasFatalError()) {
if (redo != null && !fUndoNames.isEmpty()) {
fRedoNames.push(fUndoNames.pop());
fRedoChanges.push(redo);
fireUndoStackChanged();
fireRedoStackChanged();
} else {
flush();
}
} else {
flush();
}
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public void performRedo(IValidationCheckResultQuery query, IProgressMonitor pm) throws CoreException {
if (pm == null)
pm= new NullProgressMonitor();
RefactoringStatus result= new RefactoringStatus();
if (fRedoChanges.empty())
return;
Change change= (Change)fRedoChanges.pop();
if (query == null)
query= new NullQuery();
Change undo;
try {
undo= executeChange(result, change, query, pm);
} catch (InterruptedException e) {
fRedoChanges.push(change);
return;
}
if (!result.hasFatalError()) {
if (undo != null && !fRedoNames.isEmpty()) {
fUndoNames.push(fRedoNames.pop());
fUndoChanges.push(undo);
fireRedoStackChanged();
fireUndoStackChanged();
}
} else {
flush();
}
}
private Change executeChange(final RefactoringStatus status, final Change change, final IValidationCheckResultQuery query, IProgressMonitor pm) throws CoreException, InterruptedException {
final Change[] undo= new Change[1];
final boolean[] interrupted= new boolean[1];
IWorkspaceRunnable runnable= new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
boolean undoInitialized= false;
try {
monitor.beginTask("", 11); //$NON-NLS-1$
status.merge(change.isValid(new SubProgressMonitor(monitor, 2)));
if (status.hasFatalError()) {
query.stopped(status);
change.dispose();
return;
}
if (!status.isOK() && !query.proceed(status)) {
interrupted[0]= true;
return;
}
ResourcesPlugin.getWorkspace().checkpoint(false);
boolean successful= false;
try {
aboutToPerformChange(change);
undo[0]= change.perform(new SubProgressMonitor(monitor, 8));
successful= true;
ResourcesPlugin.getWorkspace().checkpoint(false);
} finally {
changePerformed(change, successful);
}
change.dispose();
if (undo[0] != null) {
undo[0].initializeValidationData(new SubProgressMonitor(monitor, 1));
undoInitialized= true;
}
} catch (CoreException e) {
flush();
if (undo[0] != null && undoInitialized) {
Change ch= undo[0];
undo[0]= null;
ch.dispose();
} else {
undo[0]= null;
}
throw e;
} catch (RuntimeException e) {
flush();
if (undo[0] != null && undoInitialized) {
Change ch= undo[0];
undo[0]= null;
ch.dispose();
} else {
undo[0]= null;
}
throw e;
} finally {
monitor.done();
}
}
};
ResourcesPlugin.getWorkspace().run(runnable, pm);
if (interrupted[0])
throw new InterruptedException();
return undo[0];
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public boolean anythingToRedo() {
return !fRedoChanges.empty();
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public boolean anythingToUndo() {
return !fUndoChanges.empty();
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public String peekUndoName() {
if (fUndoNames.size() > 0)
return (String)fUndoNames.peek();
return null;
}
/*
* (Non-Javadoc) Method declared in IUndoManager.
*/
public String peekRedoName() {
if (fRedoNames.size() > 0)
return (String)fRedoNames.peek();
return null;
}
private void fireUndoStackChanged() {
if (fListeners == null)
return;
Object[] listeners= fListeners.getListeners();
for (int i= 0; i < listeners.length; i++) {
((IUndoManagerListener)listeners[i]).undoStackChanged(this);
}
}
private void fireRedoStackChanged() {
if (fListeners == null)
return;
Object[] listeners= fListeners.getListeners();
for (int i= 0; i < listeners.length; i++) {
((IUndoManagerListener)listeners[i]).redoStackChanged(this);
}
}
private void sendDispose(Collection collection) {
for (Iterator iter= collection.iterator(); iter.hasNext();) {
final Change change= (Change)iter.next();
ISafeRunnable r= new ISafeRunnable() {
public void run() {
change.dispose();
}
public void handleException(Throwable exception) {
RefactoringCorePlugin.log(exception);
}
};
SafeRunner.run(r);
}
}
//---- testing methods ---------------------------------------------
public boolean testHasNumberOfUndos(int number) {
return fUndoChanges.size() == number;
}
public boolean testHasNumberOfRedos(int number) {
return fRedoChanges.size() == number;
}
}