/*******************************************************************************
* Copyright (c) 2000, 2009 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.team.core.synchronize;
import java.util.*;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.team.core.ITeamStatus;
import org.eclipse.team.core.TeamStatus;
import org.eclipse.team.core.synchronize.FastSyncInfoFilter.SyncInfoDirectionFilter;
import org.eclipse.team.internal.core.Policy;
import org.eclipse.team.internal.core.TeamPlugin;
import org.eclipse.team.internal.core.subscribers.SyncInfoStatistics;
/**
* A dynamic collection of {@link SyncInfo} objects that provides
* change notification to registered listeners. Batching of change notifications
* can be accomplished using the beginInput/endInput
methods.
*
* @see SyncInfoTree
* @see SyncInfo
* @see ISyncInfoSetChangeListener
* @since 3.0
*/
public class SyncInfoSet {
// fields used to hold resources of interest
// {IPath -> SyncInfo}
private Map resources = Collections.synchronizedMap(new HashMap());
// keep track of number of sync kinds in the set
private SyncInfoStatistics statistics = new SyncInfoStatistics();
// keep track of errors that occurred while trying to populate the set
private Map errors = new HashMap();
private boolean lockedForModification;
/**
* Create an empty set.
*/
public SyncInfoSet() {
}
/**
* Create a SyncInfoSet
containing the given SyncInfo
* instances.
*
* @param infos the SyncInfo
instances to be contained by this set
*/
public SyncInfoSet(SyncInfo[] infos) {
this();
// use the internal add since we can't have listeners at this point anyway
for (int i = 0; i < infos.length; i++) {
internalAdd(infos[i]);
}
}
/**
* Return an array of SyncInfo
for all out-of-sync resources that are contained by the set.
*
* @return an array of SyncInfo
*/
public synchronized SyncInfo[] getSyncInfos() {
return (SyncInfo[]) resources.values().toArray(new SyncInfo[resources.size()]);
}
/**
* Return all out-of-sync resources contained in this set. The default implementation
* uses getSyncInfos()
to determine the resources contained in the set.
* Subclasses may override to optimize.
*
* @return all out-of-sync resources contained in the set
*/
public IResource[] getResources() {
SyncInfo[] infos = getSyncInfos();
List resources = new ArrayList();
for (int i = 0; i < infos.length; i++) {
SyncInfo info = infos[i];
resources.add(info.getLocal());
}
return (IResource[]) resources.toArray(new IResource[resources.size()]);
}
/**
* Return the SyncInfo
for the given resource or null
* if the resource is not contained in the set.
*
* @param resource the resource
* @return the SyncInfo
for the resource or null
if
* the resource is in-sync or doesn't have synchronization information in this set.
*/
public synchronized SyncInfo getSyncInfo(IResource resource) {
return (SyncInfo)resources.get(resource.getFullPath());
}
/**
* Return the number of out-of-sync resources contained in this set.
*
* @return the size of the set.
* @see #countFor(int, int)
*/
public synchronized int size() {
return resources.size();
}
/**
* Return the number of out-of-sync resources in the given set whose sync kind
* matches the given kind and mask (e.g. (SyncInfo#getKind() & mask) == kind
).
*
* For example, this will return the number of outgoing changes in the set: *
* long outgoing = countFor(SyncInfo.OUTGOING, SyncInfo.DIRECTION_MASK); ** * @param kind the sync kind * @param mask the sync kind mask * @return the number of matching resources in the set. */ public long countFor(int kind, int mask) { return statistics.countFor(kind, mask); } /** * Returns
true
if there are any conflicting nodes in the set, and
* false
otherwise.
*
* @return true
if there are any conflicting nodes in the set, and
* false
otherwise.
*/
public boolean hasConflicts() {
return countFor(SyncInfo.CONFLICTING, SyncInfo.DIRECTION_MASK) > 0;
}
/**
* Return whether the set is empty.
*
* @return true
if the set is empty
*/
public synchronized boolean isEmpty() {
return resources.isEmpty();
}
/**
* Add the SyncInfo
to the set, replacing any previously existing one.
*
* @param info the new SyncInfo
*/
protected synchronized void internalAdd(SyncInfo info) {
Assert.isTrue(!lockedForModification);
IResource local = info.getLocal();
IPath path = local.getFullPath();
SyncInfo oldSyncInfo = (SyncInfo)resources.put(path, info);
if(oldSyncInfo == null) {
statistics.add(info);
} else {
statistics.remove(oldSyncInfo);
statistics.add(info);
}
}
/**
* Remove the resource from the set, updating all internal data structures.
*
* @param resource the resource to be removed
* @return the SyncInfo
that was just removed
*/
protected synchronized SyncInfo internalRemove(IResource resource) {
Assert.isTrue(!lockedForModification);
IPath path = resource.getFullPath();
SyncInfo info = (SyncInfo)resources.remove(path);
if (info != null) {
statistics.remove(info);
}
return info;
}
/**
* Registers the given listener for sync info set notifications. Has
* no effect if an identical listener is already registered.
*
* @param listener listener to register
*/
public void addSyncSetChangedListener(ISyncInfoSetChangeListener listener) {
synchronized(listeners) {
listeners.add(listener);
}
}
/**
* Removes the given listener from participant notifications. Has
* no effect if listener is not already registered.
*
* @param listener listener to remove
*/
public void removeSyncSetChangedListener(ISyncInfoSetChangeListener listener) {
synchronized(listeners) {
listeners.remove(listener);
}
}
/**
* Reset the sync set so it is empty. Listeners are notified of the change.
*/
public void clear() {
try {
beginInput();
errors.clear();
resources.clear();
statistics.clear();
getChangeEvent().reset();
} finally {
endInput(null);
}
}
/*
* Run the given runnable. This operation
* will block other threads from modifying the
* set and postpone any change notifications until after the runnable
* has been executed. Mutable subclasses must override.
*
* The given runnable may be run in the same thread as the caller or
* more be run asynchronously in another thread at the discretion of the
* subclass implementation. However, it is guaranteed that two invocations
* of run
performed in the same thread will be executed in the
* same order even if run in different threads.
*
null
*/
private void run(IWorkspaceRunnable runnable, IProgressMonitor monitor) {
monitor = Policy.monitorFor(monitor);
monitor.beginTask(null, 100);
try {
beginInput();
runnable.run(Policy.subMonitorFor(monitor, 80));
} catch (CoreException e) {
addError(new TeamStatus(IStatus.ERROR, TeamPlugin.ID, ITeamStatus.SYNC_INFO_SET_ERROR, e.getMessage(), e, null));
} finally {
endInput(Policy.subMonitorFor(monitor, 20));
}
}
/**
* Connect the listener to the sync set in such a fashion that the listener will
* be connected the the sync set using addChangeListener
* and issued a reset event. This is done to provide a means of connecting to the
* sync set and initializing a model based on the sync set without worrying about
* missing events.
*
* The reset event may be done in the context of this method invocation or may be
* done in another thread at the discretion of the SyncInfoSet
* implementation.
*
* Disconnecting is done by calling removeChangeListener
. Once disconnected,
* a listener can reconnect to be re-initialized.
*
SyncInfo
to the set. A change event will
* be generated unless the call to this method is nested in between calls
* to beginInput()
and endInput(IProgressMonitor)
* in which case the event for this addition and any other sync set
* change will be fired in a batched event when endInput
* is invoked.
*
* Invoking this method outside of the above mentioned block will result
* in the endInput(IProgressMonitor)
being invoked with a null
* progress monitor. If responsiveness is required, the client should always
* nest sync set modifications within beginInput/endInput
.
*
true
if this sync set has incoming changes.
* Note that conflicts are not considered to be incoming changes.
*
* @return true
if this sync set has incoming changes.
*/
public boolean hasIncomingChanges() {
return countFor(SyncInfo.INCOMING, SyncInfo.DIRECTION_MASK) > 0;
}
/**
* Returns true
if this sync set has outgoing changes.
* Note that conflicts are not considered to be outgoing changes.
*
* @return true
if this sync set has outgoing changes.
*/
public boolean hasOutgoingChanges() {
return countFor(SyncInfo.OUTGOING, SyncInfo.DIRECTION_MASK) > 0;
}
/**
* This method is used to obtain a lock on the set which ensures thread safety
* and batches change notification. If the set is locked by another thread,
* the calling thread will block until the lock
* becomes available. This method uses an org.eclipse.core.runtime.jobs.ILock
.
*
* It is important that the lock is released after it is obtained. Calls to endInput
* should be done in a finally block as illustrated in the following code snippet.
*
* try { * set.beginInput(); * // do stuff * } finally { * set.endInput(progress); * } **
* Calls to beginInput
and endInput
can be nested and must be matched.
*
SyncInfo
for one
* or more resources due to an exception or some other problem. Listeners
* will be notified that an error occurred and can react accordingly.
*
* Only one error can be associated with a resource (which is obtained from
* the ITeamStatus
). It is up to the
* client populating the set to ensure that the error associated with a
* resource contains all relevant information.
* The error will remain in the set until the set is reset.
*
SyncInfo
* contained in this set.
* @return an iterator over all SyncInfo
* contained in this set.
* @since 3.1
*/
public Iterator iterator() {
return resources.values().iterator();
}
}