blob: 18c5a59c99cdcda94ab1001d7497003144661418 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v0.5
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v05.html
*
* Contributors:
* IBM - Initial implementation
******************************************************************************/
package org.eclipse.team.internal.ui.target;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.sync.IRemoteResource;
import org.eclipse.team.internal.core.target.TargetManager;
import org.eclipse.team.internal.core.target.TargetProvider;
import org.eclipse.team.internal.ui.Policy;
import org.eclipse.team.internal.ui.TeamUIPlugin;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.team.internal.ui.sync.ChangedTeamContainer;
import org.eclipse.team.internal.ui.sync.ITeamNode;
import org.eclipse.team.internal.ui.sync.SyncSet;
import org.eclipse.team.internal.ui.sync.UnchangedTeamContainer;
public abstract class TargetSyncAction extends Action {
private TargetSyncCompareInput diffModel;
private ISelectionProvider selectionProvider;
protected int syncMode;
private Shell shell;
protected static IRemoteResource getRemoteResourceFor(IResource local) throws TeamException {
return TargetManager.getProvider(local.getProject()).getRemoteSyncElement(local).getRemote();
}
/**
* Creates a TargetSyncAction which works on selection and doesn't commit changes.
*/
public TargetSyncAction(TargetSyncCompareInput model, ISelectionProvider sp, String label, Shell shell) {
super(label);
this.diffModel = model;
this.selectionProvider = sp;
this.shell = shell;
}
protected Shell getShell() {
return shell;
}
protected TargetSyncCompareInput getDiffModel() {
return diffModel;
}
/**
* Returns true if at least one node can perform the specified action.
*/
private boolean isEnabled(Object[] nodes) {
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] instanceof ITeamNode) {
ITeamNode node = (ITeamNode)nodes[i];
if (isEnabled(node)) {
return true;
}
} else {
if (nodes[i] instanceof IDiffContainer)
if (isEnabled(((IDiffContainer)nodes[i]).getChildren()))
return true;
}
}
return false;
}
protected abstract boolean isEnabled(ITeamNode node);
/**
* Perform the sychronization operation.
*/
public void run() {
ISelection s = selectionProvider.getSelection();
if (!(s instanceof IStructuredSelection) || s.isEmpty()) {
return;
}
final SyncSet set = new SyncSet((IStructuredSelection)s);
removeNonApplicableNodes(set, syncMode);
final SyncSet[] result = new SyncSet[1];
WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
public void execute(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
result[0] = TargetSyncAction.this.run(set, monitor);
}
};
try {
run(op, Policy.bind("MergeAction.problemsDuringSync")); //$NON-NLS-1$
} catch (InterruptedException e) {
}
if (result[0] != null) {
// all returned nodes that have a changed sync kind are assumed
// to have been operated on and will be removed from the diff tree.
removeNodes(result[0].getChangedNodes());
// any node that claims that it's IN_SYNC will be automatically
// filtered from the diff tree - see DiffElement.setKind().
diffModel.updateView();
}
}
protected abstract void removeNonApplicableNodes(SyncSet set, int syncMode);
/**
* The given nodes have been synchronized. Remove them from
* the sync set.
*
* For folders that are outgoing deletions, we may need to leave the
* folder as is or adjust the sync kind depending on the sync kind of
* the folder's children.
*
* @see CVSSyncCompareInput#collectResourceChanges(IDiffContainer, IRemoteSyncElement, IProgressMonitor)
*/
private void removeNodes(final ITeamNode[] nodes) {
// Update the model
Set outgoingFolderDeletions = new HashSet();
for (int i = 0; i < nodes.length; i++) {
if (nodes[i].getClass() == UnchangedTeamContainer.class) {
// Unchanged containers get removed automatically when all
// children are removed
continue;
}
if (nodes[i].getClass() == ChangedTeamContainer.class) {
// If this node still has children, convert to an
// unchanged container, then it will disappear when
// all children have been removed.
ChangedTeamContainer container = (ChangedTeamContainer)nodes[i];
IDiffElement[] children = container.getChildren();
if (children.length > 0) {
IDiffContainer parent = container.getParent();
UnchangedTeamContainer unchanged = new UnchangedTeamContainer(parent, container.getResource());
for (int j = 0; j < children.length; j++) {
unchanged.add(children[j]);
}
parent.removeToRoot(container);
continue;
}
// No children, it will get removed below.
}
nodes[i].getParent().removeToRoot(nodes[i]);
}
}
/**
* Updates the action with the latest selection, setting enablement
* as necessary.
*/
public void update(int syncMode) {
this.syncMode = syncMode;
IStructuredSelection selection = (IStructuredSelection)selectionProvider.getSelection();
setEnabled(isEnabled(selection.toArray()));
}
/**
* Subclasses must implement this method, which performs action-specific code.
*
* It may return the sync set which was passed in, or null.
*/
protected abstract SyncSet run(SyncSet syncSet, IProgressMonitor monitor);
/**
* Helper method to run a runnable in a progress monitor dialog, and display any errors.
*/
protected void run(IRunnableWithProgress op, String problemMessage) throws InterruptedException {
ProgressMonitorDialog dialog = new ProgressMonitorDialog(getShell());
try {
dialog.run(true, true, op);
} catch (InvocationTargetException e) {
Throwable throwable = e.getTargetException();
IStatus error = null;
if (throwable instanceof CoreException) {
error = ((CoreException)throwable).getStatus();
} else {
error = new Status(IStatus.ERROR, TeamUIPlugin.ID, 1, Policy.bind("simpleInternal") , throwable); //$NON-NLS-1$
}
ErrorDialog.openError(shell, problemMessage, error.getMessage(), error);
TeamUIPlugin.log(error);
}
}
/**
* Helper method. Check if a save is necessary. If it is, prompt the user to save.
* Return true if all necessary saves have been performed, false otherwise.
*/
protected boolean saveIfNecessary() {
return getDiffModel().saveIfNecessary();
}
/**
* Convenience method that maps the given resources to their target providers.
* The returned Hashtable has keys which are TargetProviders, and values
* which are Lists of IResources that are shared with that provider.
*
* @return a hashtable mapping providers to their resources
*/
protected Hashtable getTargetProviderMapping(IResource[] resources) throws TeamException {
Hashtable result = new Hashtable();
for (int i = 0; i < resources.length; i++) {
TargetProvider provider = TargetManager.getProvider(resources[i].getProject());
List list = (List)result.get(provider);
if (list == null) {
list = new ArrayList();
result.put(provider, list);
}
list.add(resources[i]);
}
return result;
}
}