blob: 74073f086335ed1120bcb776bacdd70028bd1286 [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.core.refactoring;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
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.OperationCanceledException;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin;
/**
* Represents a composite change. Composite changes can be marked
* as synthetic. A synthetic composite changes might not be rendered
* in the refactoring preview tree to save display real-estate.
* <p>
* Clients may subclass this class.
* </p>
*
* @see Change
*
* @since 3.0
*/
public class CompositeChange extends Change {
private String fName;
private List fChanges;
private boolean fIsSynthetic;
private Change fUndoUntilException;
/**
* Creates a new composite change with the given name.
*
* @param name the human readable name of the change. Will
* be used to display the change in the user interface
*/
public CompositeChange(String name) {
this(name, new ArrayList(2));
}
/**
* Creates a new composite change with the given name and array
* of children.
*
* @param name the human readable name of the change. Will
* be used to display the change in the user interface
* @param children the initial array of children
*/
public CompositeChange(String name, Change[] children) {
this(name, new ArrayList(children.length));
addAll(children);
}
private CompositeChange(String name, List changes) {
Assert.isNotNull(name);
Assert.isNotNull(changes);
fName= name;
fChanges= changes;
}
/**
* Returns whether this change is synthetic or not.
*
* @return <code>true</code>if this change is synthetic; otherwise
* <code>false</code>
*/
public boolean isSynthetic() {
return fIsSynthetic;
}
/**
* Marks this change as synthetic.
*/
public void markAsSynthetic() {
fIsSynthetic= true;
}
/**
* {@inheritDoc}
*/
public String getName() {
return fName;
}
/**
* Adds the given change to the list of children. The change to be added
* can be <code>null</code>. Adding a "null" change does nothing.
*
* @param change the change to add
*/
public void add(Change change) {
if (change != null) {
Assert.isTrue(change.getParent() == null);
fChanges.add(change);
change.setParent(this);
}
}
/**
* Adds all changes in the given array to the list of children.
*
* @param changes the changes to add
*/
public void addAll(Change[] changes) {
for (int i= 0; i < changes.length; i++) {
add(changes[i]);
}
}
/**
* Merges the children of the given composite change into this
* change. This means the changes are removed from the given
* composite change and added to this change.
*
* @param change the change to merge
*/
public void merge(CompositeChange change) {
Change[] others= change.getChildren();
for (int i= 0; i < others.length; i++) {
Change other= others[i];
change.remove(other);
add(other);
}
}
/**
* Removes the given change from the list of children.
*
* @param change the change to remove
*
* @return <code>true</code> if the change contained the given
* child; otherwise <code>false</code> is returned
*/
public boolean remove(Change change) {
Assert.isNotNull(change);
boolean result= fChanges.remove(change);
if (result) {
change.setParent(null);
}
return result;
}
/**
* Removes all changes from this composite change.
*
* @return the list of changes removed from this composite
* change
*
* @since 3.1
*/
public Change[] clear() {
Change[] result= (Change[])fChanges.toArray(new Change[fChanges.size()]);
fChanges.clear();
return result;
}
/**
* Returns the children managed by this composite change.
*
* @return the children of this change or an empty array if no
* children exist
*/
public Change[] getChildren() {
return (Change[])fChanges.toArray(new Change[fChanges.size()]);
}
/**
* {@inheritDoc}
* <p>
* The composite change sends <code>setEnabled</code> to all its children.
* </p>
* <p>
* Client are allowed to extend this method.
* </p>
*/
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
for (Iterator iter= fChanges.iterator(); iter.hasNext(); ) {
((Change)iter.next()).setEnabled(enabled);
}
}
/**
* {@inheritDoc}
* <p>
* The composite change sends <code>initializeValidationData</code> to all its
* children.
* </p>
* <p>
* Client are allowed to extend this method.
* </p>
*/
public void initializeValidationData(IProgressMonitor pm) {
pm.beginTask("", fChanges.size()); //$NON-NLS-1$
for (Iterator iter= fChanges.iterator(); iter.hasNext();) {
Change change= (Change)iter.next();
change.initializeValidationData(new SubProgressMonitor(pm, 1));
pm.worked(1);
}
}
/**
* {@inheritDoc}
* <p>
* The composite change sends <code>isValid</code> to all its children
* until the first one returns a status with a severity of <code>FATAL
* </code>. If one of the children throws an exception the remaining children
* will not receive the <code>isValid</code> call.
* </p>
* <p>
* Client are allowed to extend this method.
* </p>
*/
public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException {
RefactoringStatus result= new RefactoringStatus();
pm.beginTask("", fChanges.size()); //$NON-NLS-1$
for (Iterator iter= fChanges.iterator(); iter.hasNext() && !result.hasFatalError();) {
Change change= (Change)iter.next();
if (change.isEnabled())
result.merge(change.isValid(new SubProgressMonitor(pm, 1)));
else
pm.worked(1);
if (pm.isCanceled())
throw new OperationCanceledException();
}
pm.done();
return result;
}
/**
* {@inheritDoc}
* <p>
* The composite change sends <code>perform</code> to all its <em>enabled</em>
* children. If one of the children throws an exception the remaining children
* will not receive the <code>perform</code> call. In this case the method <code>
* getUndoUntilException</code> can be used to get an undo object containing the
* undo objects of all executed children.
* </p>
* <p>
* Client are allowed to extend this method.
* </p>
*/
public Change perform(IProgressMonitor pm) throws CoreException {
fUndoUntilException= null;
List undos= new ArrayList(fChanges.size());
pm.beginTask("", fChanges.size()); //$NON-NLS-1$
pm.setTaskName(RefactoringCoreMessages.CompositeChange_performingChangesTask_name);
Change change= null;
boolean canceled= false;
try {
for (Iterator iter= fChanges.iterator(); iter.hasNext();) {
change= (Change)iter.next();
if (canceled && !internalProcessOnCancel(change))
continue;
if (change.isEnabled()) {
Change undoChange= null;
try {
undoChange= change.perform(new SubProgressMonitor(pm, 1));
} catch(OperationCanceledException e) {
canceled= true;
if (!internalContinueOnCancel())
throw e;
undos= null;
}
if (undos != null) {
if (undoChange == null) {
undos= null;
} else {
undos.add(undoChange);
}
}
}
// remove the change from the list of children to give
// the garbage collector the change to collect the change. This
// ensures that the memory consumption doesn't go up when
// producing the undo change tree.
iter.remove();
// Make sure we dispose the change since it will now longer be
// in the list of children when call CompositeChange#dispose()
final Change changeToDispose= change;
SafeRunner.run(new ISafeRunnable() {
public void run() throws Exception {
changeToDispose.dispose();
}
public void handleException(Throwable exception) {
RefactoringCorePlugin.log(exception);
}
});
}
if (canceled)
throw new OperationCanceledException();
if (undos != null) {
Collections.reverse(undos);
return createUndoChange((Change[]) undos.toArray(new Change[undos.size()]));
} else {
return null;
}
} catch (CoreException e) {
handleUndos(change, undos);
internalHandleException(change, e);
throw e;
} catch (RuntimeException e) {
handleUndos(change, undos);
internalHandleException(change, e);
throw e;
}
}
private void handleUndos(Change failedChange, List undos) {
if (undos == null) {
fUndoUntilException= null;
return;
}
if (failedChange instanceof CompositeChange) {
Change partUndoChange= ((CompositeChange)failedChange).getUndoUntilException();
if (partUndoChange != null) {
undos.add(partUndoChange);
}
}
if (undos.size() == 0) {
fUndoUntilException= new NullChange(getName());
return;
}
Collections.reverse(undos);
fUndoUntilException= createUndoChange((Change[]) undos.toArray(new Change[undos.size()]));
}
/**
* Note: this is an internal method and should not be overridden outside of
* the refactoring framework.
* <p>
* The method gets called if one of the changes managed by this
* composite change generates an exception when performed.
* </p>
*
* @param change the change that caused the exception
* @param t the exception itself
*/
protected void internalHandleException(Change change, Throwable t) {
// do nothing
}
/**
* Note: this is an internal method and should not be overridden outside of
* the refactoring framework.
* <p>
* The method gets called if one of the changes managed by this
* composite change generates an operation canceled exception when
* performed.
* </p>
*
* @return <code>true</code> if performing the change should
* continue on cancel; otherwise <code>false</code>
*
* @since 3.1
*/
protected boolean internalContinueOnCancel() {
return false;
}
/**
* Note: this is an internal method and should not be overridden outside of
* the refactoring framework.
* <p>
* The method gets called if the execution of this change got canceled,
* but <code>internalContinueOnCancel</code> returned true.
* </p>
*
* @param change the change to perform
*
* @return <code>true</code> if the given change should be performed although
* the execution got canceled; otherwise <code>false</code>
*
* @since 3.1
*/
protected boolean internalProcessOnCancel(Change change) {
return false;
}
/**
* {@inheritDoc}
* <p>
* The composite change sends <code>dispose</code> to all its children. It is guaranteed
* that all children receive the <code>dispose</code> call.
* </p>
*/
public void dispose() {
for (Iterator iter= fChanges.iterator(); iter.hasNext(); ) {
final Change change= (Change)iter.next();
SafeRunner.run(new ISafeRunnable() {
public void run() throws Exception {
change.dispose();
}
public void handleException(Throwable exception) {
RefactoringCorePlugin.log(exception);
}
});
}
}
/**
* Returns the undo object containing all undo changes of those children
* that got successfully executed while performing this change. Returns
* <code>null</code> if all changes were executed successfully.
* <p>
* This method is not intended to be overridden or extended.
* </p>
* @return the undo object containing all undo changes of those children
* that got successfully executed while performing this change
*/
public Change getUndoUntilException() {
return fUndoUntilException;
}
/**
* Hook to create an undo change. The method should be overridden
* by clients which provide their own composite change to create
* a corresponding undo change.
*
* @param childUndos the child undo. The undo edits appear in the
* list in the reverse order of their execution. So the first
* change in the array is the undo change of the last change
* that got executed.
*
* @return the undo change
*/
protected Change createUndoChange(Change[] childUndos) {
return new CompositeChange(getName(), childUndos);
}
/**
* {@inheritDoc}
*/
public Object[] getAffectedObjects() {
if (fChanges.size() == 0)
return new Object[0];
List result= new ArrayList();
for (Iterator iter= fChanges.iterator(); iter.hasNext();) {
Change change= (Change)iter.next();
Object[] affectedObjects= change.getAffectedObjects();
if (affectedObjects == null)
return null;
result.addAll(Arrays.asList(affectedObjects));
}
return result.toArray();
}
/**
* {@inheritDoc}
*/
public Object getModifiedElement() {
return null;
}
/**
* {@inheritDoc}
*
* @since 3.2
*/
public ChangeDescriptor getDescriptor() {
for (final Iterator iterator= fChanges.iterator(); iterator.hasNext();) {
final Change change= (Change) iterator.next();
final ChangeDescriptor descriptor= change.getDescriptor();
if (descriptor != null)
return descriptor;
}
return null;
}
public String toString() {
StringBuffer buff= new StringBuffer();
buff.append(getName());
buff.append("\n"); //$NON-NLS-1$
for (Iterator iter= fChanges.iterator(); iter.hasNext();) {
buff.append("<").append(iter.next().toString()).append("/>\n"); //$NON-NLS-2$ //$NON-NLS-1$
}
return buff.toString();
}
}