| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ltk.core.refactoring; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| 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.Platform; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| |
| import org.eclipse.ltk.internal.core.refactoring.Assert; |
| 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(changes); |
| Assert.isNotNull(name); |
| fChanges= changes; |
| fName= name; |
| } |
| |
| /** |
| * 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; |
| |
| } |
| |
| /** |
| * Returns the children managed by this composite change. |
| * |
| * @return the children |
| */ |
| public Change[] getChildren() { |
| if (fChanges == null) |
| return null; |
| 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) { |
| 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</code> |
| * 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 |
| * undos 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.getString("CompositeChange.performingChangesTask.name")); //$NON-NLS-1$ |
| Change change= null; |
| try { |
| for (Iterator iter= fChanges.iterator(); iter.hasNext();) { |
| change= (Change)iter.next(); |
| if (change.isEnabled()) { |
| Change undoChange= change.perform(new SubProgressMonitor(pm, 1)); |
| 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(); |
| } |
| 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 refacotring framework. |
| * <p> |
| * The method gets called if one of the changes managed by this |
| * composite change generates and 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 |
| } |
| |
| /** |
| * {@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> |
| * <p> |
| * Client are allowed to extend this method. |
| * </p> |
| */ |
| public void dispose() { |
| for (Iterator iter= fChanges.iterator(); iter.hasNext(); ) { |
| final Change change= (Change)iter.next(); |
| Platform.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 undos 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 getModifiedElement() { |
| 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(); |
| } |
| } |