blob: 7113f73cae4c083726eb5cdade4a5d1078afe1dd [file] [log] [blame]
package org.eclipse.team.core.subscribers;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.sync.IRemoteResource;
import org.eclipse.team.internal.core.Assert;
import org.eclipse.team.internal.core.Policy;
/**
* Describes the relative synchronization of a <b>remote</b>
* resource and a <b>local</b> resource using a <b>base</b>
* resource for comparison.
* <p>
* Differences between the base and local resources
* are classified as <b>outgoing changes</b>; if there is
* a difference, the local resource is considered the
* <b>outgoing resource</b>.
* </p>
* <p>
* Differences between the base and remote resources
* are classified as <b>incoming changes</b>; if there is
* a difference, the remote resource is considered the
* <b>incoming resource</b>.
* </p>
* <p>
* Differences between the local and remote resources
* determine the <b>sync status</b>. The sync status does
* not take into account the common resource.
* </p>
* <p>
* Note that under this parse of the world, a resource
* can have both incoming and outgoing changes at the
* same time, but may nevertheless be in sync!
* <p>
* [Issue: "Gender changes" are also an interesting aspect...
* ]
* </p>
*/
public class SyncInfo implements IAdaptable {
/*====================================================================
* Constants defining synchronization types:
*====================================================================*/
/**
* Sync constant (value 0) indicating element is in sync.
*/
public static final int IN_SYNC = 0;
/**
* Sync constant (value 1) indicating that one side was added.
*/
public static final int ADDITION = 1;
/**
* Sync constant (value 2) indicating that one side was deleted.
*/
public static final int DELETION = 2;
/**
* Sync constant (value 3) indicating that one side was changed.
*/
public static final int CHANGE = 3;
/**
* Bit mask for extracting the change type.
*/
public static final int CHANGE_MASK = CHANGE;
/*====================================================================
* Constants defining synchronization direction:
*====================================================================*/
/**
* Sync constant (value 4) indicating a change to the local resource.
*/
public static final int OUTGOING = 4;
/**
* Sync constant (value 8) indicating a change to the remote resource.
*/
public static final int INCOMING = 8;
/**
* Sync constant (value 12) indicating a change to both the remote and local resources.
*/
public static final int CONFLICTING = 12;
/**
* Bit mask for extracting the synchronization direction.
*/
public static final int DIRECTION_MASK = CONFLICTING;
/*====================================================================
* Constants defining synchronization conflict types:
*====================================================================*/
/**
* Sync constant (value 16) indication that both the local and remote resources have changed
* relative to the base but their contents are the same.
*/
public static final int PSEUDO_CONFLICT = 16;
/**
* Sync constant (value 32) indicating that both the local and remote resources have changed
* relative to the base but their content changes do not conflict (e.g. source file changes on different
* lines). These conflicts could be merged automatically.
*/
public static final int AUTOMERGE_CONFLICT = 32;
/**
* Sync constant (value 64) indicating that both the local and remote resources have changed relative
* to the base and their content changes conflict (e.g. local and remote resource have changes on
* same lines). These conflicts can only be correctly resolved by the user.
*/
public static final int MANUAL_CONFLICT = 64;
/*====================================================================
* Members:
*====================================================================*/
private IResource local;
private IRemoteResource base;
private IRemoteResource remote;
private TeamSubscriber subscriber;
private int syncKind;
/**
* Construct a sync info object.
*/
public SyncInfo(IResource local, IRemoteResource base, IRemoteResource remote, TeamSubscriber subscriber, IProgressMonitor monitor) throws TeamException {
this.local = local;
this.base = base;
this.remote = remote;
this.subscriber = subscriber;
this.syncKind = calculateKind(monitor);
}
/**
* Returns the state of the local resource. Note that the
* resource may or may not exist.
*
* @return a resource
*/
public IResource getLocal() {
return local;
}
/**
* Returns the content identifier for the local resource or <code>null</code> if
* it doesn't have one. For example, in CVS this would be the revision number.
*
* @return String that could be displayed to the user to identify this resource.
*/
public String getLocalContentIdentifier() {
return null;
}
/**
* Returns the remote resource handle for the base resource,
* or <code>null</code> if the base resource does not exist.
* <p>
* [Note: The type of the common resource may be different from the types
* of the local and remote resources.
* ]
* </p>
*
* @return a remote resource handle, or <code>null</code>
*/
public IRemoteResource getBase() {
return base;
}
/**
* Returns the handle for the remote resource,
* or <code>null</code> if the remote resource does not exist.
* <p>
* [Note: The type of the remote resource may be different from the types
* of the local and common resources.
* ]
* </p>
*
* @return a remote resource handle, or <code>null</code>
*/
public IRemoteResource getRemote() {
return remote;
}
/**
* Returns the subscriber that created and maintains this sync info
* object.
*/
public TeamSubscriber getSubscriber() {
return subscriber;
}
/**
* Returns the kind of synchronization for this node.
* @return
*/
public int getKind() {
return syncKind;
}
static public boolean isInSync(int kind) {
return kind == IN_SYNC;
}
static public int getDirection(int kind) {
return kind & DIRECTION_MASK;
}
static public int getChange(int kind) {
return kind & CHANGE_MASK;
}
public boolean equals(Object other) {
if(other == this) return true;
if(other instanceof SyncInfo) {
return equalNodes(this, (SyncInfo)other);
}
return false;
}
private boolean equalNodes(SyncInfo node1, SyncInfo node2) {
if(node1 == null || node2 == null) {
return false;
}
// First, ensure the local resources are equals
IResource local1 = null;
if (node1.getLocal() != null)
local1 = node1.getLocal();
IResource local2 = null;
if (node2.getLocal() != null)
local2 = node2.getLocal();
if (!equalObjects(local1, local2)) return false;
// Next, ensure the base resources are equal
IRemoteResource base1 = null;
if (node1.getBase() != null)
base1 = node1.getBase();
IRemoteResource base2 = null;
if (node2.getBase() != null)
base2 = node2.getBase();
if (!equalObjects(base1, base2)) return false;
// Finally, ensure the remote resources are equal
IRemoteResource remote1 = null;
if (node1.getRemote() != null)
remote1 = node1.getRemote();
IRemoteResource remote2 = null;
if (node2.getRemote() != null)
remote2 = node2.getRemote();
if (!equalObjects(remote1, remote2)) return false;
return true;
}
private boolean equalObjects(Object o1, Object o2) {
if (o1 == null && o2 == null) return true;
if (o1 == null || o2 == null) return false;
return o1.equals(o2);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
public Object getAdapter(Class adapter) {
if (adapter == IResource.class) {
return getLocal();
}
return null;
}
public String toString() {
return getLocal().getName() + " " + kindToString(getKind()); //$NON-NLS-1$
}
public static String kindToString(int kind) {
String label = ""; //$NON-NLS-1$
if(kind==IN_SYNC) {
label = Policy.bind("RemoteSyncElement.insync"); //$NON-NLS-1$
} else {
switch(kind & DIRECTION_MASK) {
case CONFLICTING: label = Policy.bind("RemoteSyncElement.conflicting"); break; //$NON-NLS-1$
case OUTGOING: label = Policy.bind("RemoteSyncElement.outgoing"); break; //$NON-NLS-1$
case INCOMING: label = Policy.bind("RemoteSyncElement.incoming"); break; //$NON-NLS-1$
}
switch(kind & CHANGE_MASK) {
case CHANGE: label = Policy.bind("concatStrings", label, Policy.bind("RemoteSyncElement.change")); break; //$NON-NLS-1$ //$NON-NLS-2$
case ADDITION: label = Policy.bind("concatStrings", label, Policy.bind("RemoteSyncElement.addition")); break; //$NON-NLS-1$ //$NON-NLS-2$
case DELETION: label = Policy.bind("concatStrings", label, Policy.bind("RemoteSyncElement.deletion")); break; //$NON-NLS-1$ //$NON-NLS-2$
}
if((kind & MANUAL_CONFLICT) != 0) {
label = Policy.bind("concatStrings", label, Policy.bind("RemoteSyncElement.manual")); //$NON-NLS-1$ //$NON-NLS-2$
}
if((kind & AUTOMERGE_CONFLICT) != 0) {
label = Policy.bind("concatStrings", label, Policy.bind("RemoteSyncElement.auto")); //$NON-NLS-1$ //$NON-NLS-2$
}
}
return Policy.bind("RemoteSyncElement.delimit", label); //$NON-NLS-1$
}
protected int calculateKind(IProgressMonitor progress) throws TeamException {
progress = Policy.monitorFor(progress);
int description = IN_SYNC;
ComparisonCriteria criteria = subscriber.getCurrentComparisonCriteria();
boolean localExists = local.exists();
if (subscriber.isThreeWay()) {
if (base == null) {
if (remote == null) {
if (!localExists) {
description = IN_SYNC;
} else {
description = OUTGOING | ADDITION;
}
} else {
if (!localExists) {
description = INCOMING | ADDITION;
} else {
description = CONFLICTING | ADDITION;
try {
progress.beginTask(null, 60);
if (criteria.compare(local, remote, Policy.subMonitorFor(progress, 30))) {
description |= PSEUDO_CONFLICT;
}
} finally {
progress.done();
}
}
}
} else {
if (!localExists) {
if (remote == null) {
description = CONFLICTING | DELETION | PSEUDO_CONFLICT;
} else {
if (criteria.compare(base, remote, progress))
description = OUTGOING | DELETION;
else
description = CONFLICTING | CHANGE;
}
} else {
if (remote == null) {
if (criteria.compare(local, base, progress))
description = INCOMING | DELETION;
else
description = CONFLICTING | CHANGE;
} else {
progress.beginTask(null, 90);
boolean ay = criteria.compare(local, base, Policy.subMonitorFor(progress, 30));
boolean am = criteria.compare(base, remote, Policy.subMonitorFor(progress, 30));
if (ay && am) {
// in-sync
} else if (ay && !am) {
description = INCOMING | CHANGE;
} else if (!ay && am) {
description = OUTGOING | CHANGE;
} else {
if(! criteria.compare(local, remote, Policy.subMonitorFor(progress, 30))) {
description = CONFLICTING | CHANGE;
}
}
progress.done();
}
}
}
} else { // two compare without access to base contents
if (remote == null) {
if (!localExists) {
Assert.isTrue(false);
// shouldn't happen
} else {
description= DELETION;
}
} else {
if (!localExists) {
description= ADDITION;
} else {
if (! criteria.compare(local, remote, Policy.subMonitorFor(progress, 30)))
description= CHANGE;
}
}
}
return description;
}
}