blob: e6008aea234b43ed6bb46a204ad662959ba1a5cc [file] [log] [blame]
package org.eclipse.team.internal.ui.sync;
/*
* (c) Copyright IBM Corp. 2000, 2002.
* All Rights Reserved.
*/
import java.lang.reflect.InvocationTargetException;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.structuremergeviewer.DiffContainer;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.sync.ILocalSyncElement;
import org.eclipse.team.core.sync.IRemoteSyncElement;
import org.eclipse.team.internal.ui.Policy;
import org.eclipse.team.internal.ui.TeamUIPlugin;
import org.eclipse.ui.IViewSite;
/**
* <b>Note:</b> This class/interface is part of an interim API that is still under
* development and expected to change significantly before reaching stability.
* It is being made available at this early stage to solicit feedback from pioneering
* adopters on the understanding that any code that uses this API will almost
* certainly be broken (repeatedly) as the API evolves.
*
* Performs a catchup or release operation on an array of resources.
*/
public abstract class SyncCompareInput extends CompareEditorInput {
private IRemoteSyncElement[] trees;
private CatchupReleaseViewer catchupReleaseViewer;
private DiffNode diffRoot;
private Shell shell;
private IViewSite viewSite;
private int granularity;
private ICompareInputChangeListener listener = new ICompareInputChangeListener() {
public void compareInputChanged(ICompareInput source) {
SyncCompareInput.this.compareInputChanged(source);
}
};
/**
* Subclasses may override but must call super.
*/
protected void compareInputChanged(ICompareInput source) {
catchupReleaseViewer.update(source, new String[] {CatchupReleaseViewer.PROP_KIND});
updateStatusLine();
}
/**
* Creates a new catchup or release operation.
*/
public SyncCompareInput(int granularity) {
super(new CompareConfiguration());
this.granularity = granularity;
}
/**
* Creates a new catchup or release operation.
*/
public SyncCompareInput() {
this(ILocalSyncElement.GRANULARITY_TIMESTAMP);
}
protected abstract IRemoteSyncElement[] createSyncElements(IProgressMonitor monitor) throws TeamException;
/*
* @see CompareEditorInput#createContents
*/
public Control createContents(Composite parent) {
Control result = super.createContents(parent);
initialSelectionAndExpansionState();
return result;
}
/**
* Subclasses must create and return a new CatchupReleaseViewer, and set the viewer
* using setViewer().
*/
public abstract Viewer createDiffViewer(Composite parent);
/**
* Returns the root node of the diff tree.
*/
public DiffNode getDiffRoot() {
return diffRoot;
}
/**
* Returns the first diff element that is still unresolved in the
* subtree rooted at the given root element.
* Returns null if everything is resolved.
*/
private IDiffElement getFirstChange(IDiffElement root) {
if (root instanceof ITeamNode) {
ITeamNode node = (ITeamNode)root;
if (node instanceof TeamFile) {
return node;
}
}
if (root instanceof IDiffContainer) {
IDiffElement[] children = ((IDiffContainer)root).getChildren();
IDiffElement result = null;
for (int i = 0; i < children.length; i++) {
result = getFirstChange(children[i]);
if (result != null) {
return result;
}
}
}
return null;
}
protected Shell getShell() {
return shell;
}
/**
* Returns the name of this operation.
* It is dipslayed in the CompareEditor's title bar.
*/
public String getTitle() {
return Policy.bind("SyncCompareInput.synchronize"); //$NON-NLS-1$
}
/**
* Returns the compare viewer;
*/
public CatchupReleaseViewer getViewer() {
return catchupReleaseViewer;
}
/**
* Returns the view site, or null if this is a merge.
*/
public IViewSite getViewSite() {
return viewSite;
}
/**
* Returns true if the model has incoming or conflicting changes.
*/
boolean hasIncomingChanges() {
if (diffRoot == null) {
return false;
}
SyncSet set = new SyncSet(new StructuredSelection(diffRoot.getChildren()));
return set.hasIncomingChanges() || set.hasConflicts();
}
/**
* Set an appropriate initial selection and expansion state.
*/
private void initialSelectionAndExpansionState() {
// Select the next change
IDiffElement next = getFirstChange(diffRoot);
if (next != null) {
catchupReleaseViewer.setSelection(new StructuredSelection(next), true);
} else {
catchupReleaseViewer.collapseAll();
catchupReleaseViewer.setSelection(new StructuredSelection());
}
}
/**
* Performs a compare on the given selection.
* This method is called before the CompareEditor has been opened.
* If the result of the diff is empty (or an error has occured)
* no CompareEditor is opened but an Alert is shown.
*/
public Object prepareInput(final IProgressMonitor pm) throws InterruptedException, InvocationTargetException {
if (pm.isCanceled()) {
throw new InterruptedException();
}
try {
pm.beginTask(Policy.bind("SyncCompareInput.taskTitle"), 100); //$NON-NLS-1$
// Estimate 70% of the time is creating the sync elements
this.trees = createSyncElements(Policy.subMonitorFor(pm, 70));
setMessage(null);
if (trees.length == 0) {
return null;
}
final InterruptedException[] exceptions = new InterruptedException[1];
IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
// collect changes and build the diff tree
diffRoot = new DiffNode(0);
try {
doServerDelta(monitor);
} catch (InterruptedException e) {
exceptions[0] = e;
}
}
};
if (pm.isCanceled()) {
throw new InterruptedException();
}
// Estimate 30% of the time is doing the server delta
ResourcesPlugin.getWorkspace().run(runnable, Policy.subMonitorFor(pm, 30));
if (exceptions[0] != null) throw exceptions[0];
if (pm.isCanceled()) {
throw new InterruptedException();
}
if (!diffRoot.hasChildren()) {
diffRoot = null;
}
updateStatusLine();
return diffRoot;
} catch (CoreException e) {
throw new InvocationTargetException(e);
} catch (TeamException e) {
throw new InvocationTargetException(e);
}
}
void doServerDelta(IProgressMonitor pm) throws InterruptedException {
pm.beginTask(null, trees.length * 1000);
pm.setTaskName(Policy.bind("SyncCompareInput.taskTitle")); //$NON-NLS-1$
for (int i = 0; i < trees.length; i++) {
IRemoteSyncElement tree = trees[i];
IProgressMonitor monitor = Policy.subMonitorFor(pm, 1000);
monitor.beginTask(null, 1000);
IDiffElement localRoot = collectResourceChanges(null, tree, monitor);
monitor.done();
makeParents(localRoot);
}
}
/*
* This method expects to be past a monitor that has already had it's beginTask invoked
* and has enough ticks to allow 1 unit of work per resource in the tree and an additional
* unit for each folder.
*/
protected IDiffElement collectResourceChanges(IDiffContainer parent, IRemoteSyncElement tree, IProgressMonitor pm) {
int type = tree.getSyncKind(getSyncGranularity(), Policy.subMonitorFor(pm, 1));
MergeResource mergeResource = new MergeResource(tree);
if (tree.isContainer()) {
IDiffContainer element = new ChangedTeamContainer(parent, mergeResource, type);
try {
ILocalSyncElement[] children = tree.members(Policy.subMonitorFor(pm, 1));
for (int i = 0; i < children.length; i++) {
collectResourceChanges(element, (IRemoteSyncElement)children[i], pm);
}
} catch (TeamException e) {
TeamUIPlugin.log(e.getStatus());
}
return element;
} else {
TeamFile file = new TeamFile(parent, mergeResource, type, shell);
file.addCompareInputChangeListener(listener);
return file;
}
}
public int getSyncGranularity() {
return granularity;
}
public void setSyncGranularity(int granularity) {
this.granularity = granularity;
refresh();
}
/**
* Builds a DiffFolder tree under the given root for the given resource.
*/
private DiffContainer buildPath(DiffContainer root, IContainer resource) {
DiffContainer parent = root;
if (resource.getType() == IResource.ROOT) {
return root;
}
if (resource.getType() != IResource.PROJECT) {
parent = buildPath(root, resource.getParent());
}
DiffContainer c = (DiffContainer)parent.findChild(resource.getName());
if (c == null) {
c = new UnchangedTeamContainer(parent, resource);
}
return c;
}
void makeParents(IDiffElement element) {
IContainer parent = ((ITeamNode)element).getResource().getParent();
DiffContainer container = buildPath(diffRoot, parent);
container.add(element);
}
/**
* Performs a refresh, with progress and cancelation.
*/
public void refresh() {
final Object[] input = new Object[1];
IRunnableWithProgress op = new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
input[0] = prepareInput(monitor);
}
};
try {
run(op, Policy.bind("SyncCompareInput.refresh")); //$NON-NLS-1$
} catch (InterruptedException e) {
return;
}
catchupReleaseViewer.setInput(input[0]);
if (input[0] == null) {
MessageDialog.openInformation(shell, Policy.bind("nothingToSynchronize"), Policy.bind("SyncCompareInput.nothingText")); //$NON-NLS-1$ //$NON-NLS-2$
}
}
protected void run(IRunnableWithProgress op, String problemMessage) throws InterruptedException {
ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
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);
}
}
public void setViewSite(IViewSite viewSite) {
this.viewSite = viewSite;
this.shell = viewSite.getShell();
}
public void setViewer(CatchupReleaseViewer viewer) {
this.catchupReleaseViewer = viewer;
}
/**
* Updates the status line.
*/
protected void updateStatusLine() {
if (viewSite != null && !shell.isDisposed()) {
Runnable update = new Runnable() {
public void run() {
if (!shell.isDisposed()) {
IStatusLineManager statusLine = viewSite.getActionBars().getStatusLineManager();
if (diffRoot == null) {
statusLine.setMessage(null);
statusLine.setErrorMessage(null);
return;
}
SyncSet set = new SyncSet(new StructuredSelection(diffRoot.getChildren()));
if (set.hasConflicts()) {
statusLine.setMessage(null);
statusLine.setErrorMessage(set.getStatusLineMessage());
} else {
statusLine.setErrorMessage(null);
statusLine.setMessage(set.getStatusLineMessage());
}
viewSite.getActionBars().updateActionBars();
}
}
};
// Post or run the update
if (shell.getDisplay() != Display.getCurrent()) {
shell.getDisplay().asyncExec(update);
} else {
update.run();
}
}
}
public boolean saveIfNecessary() {
if (! isSaveNeeded()) return true;
final boolean[] result = new boolean[] { false };
getShell().getDisplay().syncExec(new Runnable() {
public void run() {
try {
result[0] = MessageDialog.openQuestion(getShell(), Policy.bind("SyncView.saveTitle"),//$NON-NLS-1$
Policy.bind("SyncView.saveMessage"));//$NON-NLS-1$
if (result[0]) {
ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
saveChanges(monitor);
}
}, new NullProgressMonitor());
}
} catch (CoreException e) {
IStatus status = e.getStatus();
ErrorDialog.openError(getShell(), status.getMessage(), Policy.bind("SyncView.errorSaving"), status);//$NON-NLS-1$
result[0] = false;
}
}
});
return result[0];
}
/*
* HACK until Compare fixes dirtyness PR#14378
*/
public void saveChanges(IProgressMonitor monitor) throws CoreException {
super.saveChanges(monitor);
setDirty(false);
}
}