Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean Michel-Lemieux2004-02-25 20:45:35 +0000
committerJean Michel-Lemieux2004-02-25 20:45:35 +0000
commit611f26b53dc8c8e03019aa05a42e7ed40ad2fa51 (patch)
treeda1a682b00dda95024364683fd6e75f19c4505fe
parent4a1ca77df6f4238416a8715a8c35819611d5992a (diff)
downloadeclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.tar.gz
eclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.tar.xz
eclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.zip
SyncView API released to HEAD.
-rw-r--r--bundles/org.eclipse.team.core/.project1
-rw-r--r--bundles/org.eclipse.team.core/plugin.properties4
-rw-r--r--bundles/org.eclipse.team.core/plugin.xml1
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/DeploymentProvider.java136
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/IDeploymentProviderManager.java94
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/ITeamStatus.java39
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/ProjectSetCapability.java25
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/RepositoryProvider.java50
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/Team.java79
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/TeamException.java15
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/TeamStatus.java49
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/FilteredSyncInfoCollector.java162
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ISubscriberChangeEvent.java39
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ISubscriberChangeListener.java44
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/Subscriber.java280
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SubscriberChangeEvent.java60
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SubscriberSyncInfoCollector.java384
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/package.html61
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/CachedResourceVariant.java232
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/FastSyncInfoFilter.java192
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/IResourceVariant.java72
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/IResourceVariantComparator.java61
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoSetChangeEvent.java68
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoSetChangeListener.java74
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoTreeChangeEvent.java52
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfo.java411
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoFilter.java72
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoSet.java594
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoTree.java348
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/package.html82
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/BackgroundEventHandler.java362
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DefaultFileModificationValidator.java13
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DeploymentProviderManager.java447
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IJobListener.java26
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IMemento.java169
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java250
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCacheEntry.java217
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/Sorter.java67
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/TeamPlugin.java1
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/XMLMemento.java406
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/messages.properties18
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderDescriptor.java80
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderRegistry.java56
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/RegistryReader.java144
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/ContentComparator.java140
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberEventHandler.java431
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberSyncInfoSet.java80
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoStatistics.java107
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoTreeChangeEvent.java95
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoWorkingSetFilter.java111
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetChangedEvent.java104
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInput.java93
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSubscriber.java57
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSyncSet.java117
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/WorkingSetSyncSetInput.java36
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/DescendantResourceVariantTree.java148
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/PersistantResourceVariantTree.java160
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTree.java129
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTreeRefresh.java339
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SessionResourceVariantTree.java149
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SyncTreeSubscriber.java112
61 files changed, 8324 insertions, 121 deletions
diff --git a/bundles/org.eclipse.team.core/.project b/bundles/org.eclipse.team.core/.project
index 748544e0d..693d30d67 100644
--- a/bundles/org.eclipse.team.core/.project
+++ b/bundles/org.eclipse.team.core/.project
@@ -3,7 +3,6 @@
<name>org.eclipse.team.core</name>
<comment></comment>
<projects>
- <project>org.eclipse.core.boot</project>
<project>org.eclipse.core.resources</project>
<project>org.eclipse.core.runtime</project>
<project>org.eclipse.core.runtime.compatibility</project>
diff --git a/bundles/org.eclipse.team.core/plugin.properties b/bundles/org.eclipse.team.core/plugin.properties
index 60289eeda..2f89eff07 100644
--- a/bundles/org.eclipse.team.core/plugin.properties
+++ b/bundles/org.eclipse.team.core/plugin.properties
@@ -14,5 +14,5 @@ FileTypesRegistry=File Types Registry
GlobalIgnoreRegistry=Global Ignore Registry
TeamProjectSets=Team Project Sets
Targets=Target Provider and Location Factories
-Repository=Repository Provider Factories
-Subscriber=Sync Tree Subscriber Factories \ No newline at end of file
+Repository=Repository Providers
+Deployment=Deployment Providers \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/plugin.xml b/bundles/org.eclipse.team.core/plugin.xml
index 9a9f09c2b..bcde56282 100644
--- a/bundles/org.eclipse.team.core/plugin.xml
+++ b/bundles/org.eclipse.team.core/plugin.xml
@@ -24,6 +24,7 @@
<extension-point id="ignore" name="%GlobalIgnoreRegistry" schema="schema/ignore.exsd"/>
<extension-point id="projectSets" name="%TeamProjectSets" schema="schema/projectSets.exsd"/>
<extension-point id="repository" name="%Repository" schema="schema/repository.exsd"/>
+ <extension-point id="deployment" name="%Deployment" schema="schema/deployment.exsd"/>
<extension-point id="subscriber" name="%Subscriber"/>
<!-- Define common known file types -->
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/DeploymentProvider.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/DeploymentProvider.java
new file mode 100644
index 000000000..f4e2b9e33
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/DeploymentProvider.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * 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.team.core;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.internal.core.*;
+import org.eclipse.team.internal.core.registry.DeploymentProviderDescriptor;
+
+/**
+ * A deployment provider allows synchronization of workspace resources with a remote location. At a minimum
+ * it allows pushing resources in the workspace to a remote location and pulling resources from a
+ * remote location into the workspace.
+ * <p>
+ * The difference between a deployment provider and repository provider is the following:
+ * <ul>
+ * <li>a deployment provider doesn't have full control of workspace resources whereas the repository
+ * provider can hook into the IMoveDeleteHook and IFileModificationValidator.
+ * <li>multiple deployment providers can be mapped to the same folder whereas there is only one
+ * repository provider per project.
+ * <li>a deployment provider can be mapped to any folder
+ * whereas the repository provider must be mapped at the project.
+ * </ul>
+ * </p>
+ * <p>
+ * Deployment providers can be dfined in the plugin manifest using the following XML.
+ * <pre>
+ * &gt;extension
+ point="org.eclipse.team.core.deployment"&lt;
+ &gt;deployment
+ name="Example Deployment Provider"
+ class="org.eclipse.team.internal.example.DeploymentProviderClass"
+ id="org.eclipse.team.example.DeploymentProvider"&lt;
+ &gt;/deployment&lt;
+ &gt;/extension&lt;
+ </pre>
+ * @see RepositoryProvider
+ * @see IDeploymentProviderManager
+ * @since 3.0
+ */
+public abstract class DeploymentProvider implements IExecutableExtension, IAdaptable {
+
+ private String id;
+ private IContainer container;
+ private String name;
+
+ /**
+ * Returns the id of the deployment provider as defined in the plugin manifest
+ * @return the id
+ */
+ public String getID() {
+ return id;
+ }
+
+ /**
+ * Returns the name of the deployment provider as defined in the plugin manifest.
+ * @return the name
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Return the container (folder or project) that this provider is mapped to.
+ * @return a folder or project
+ */
+ public IContainer getMappedContainer() {
+ return this.container;
+ }
+
+ /**
+ * Method that is invoked when a deployment provider is first mapped to its folder
+ * using <code>IDeploymentProviderManager#map</code>.
+ * Mappings are persisted accross workbench invocations. However, this method is
+ * only invoked when the provider is mapped. The <code>restoreState</code> method
+ * is invoked on subsequent workbench invocations.
+ */
+ abstract public void init();
+
+ /**
+ * Method that is invoked when a providers is unmapped from a folder using
+ * <code>IDeploymentProviderManager#unmap</code>.
+ */
+ abstract public void dispose();
+
+ /**
+ * Method that is invoked after a deployment provider is first mapped or recreated on workbench
+ * invocation. This method is invoked before <code>init</code> or <code>restoreState</code>.
+ * This method is not intended to be called by clients.
+ * @param container the container the provider is mapped to
+ */
+ public final void setContainer(IContainer container) {
+ this.container = container;
+ }
+
+ abstract public void saveState(IMemento memento);
+
+ abstract public void restoreState(IMemento memento);
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object)
+ */
+ public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException {
+ // TODO: This doesn't work well since the Provider is created programmatically
+ // when initially mapped
+ this.id = config.getAttribute(DeploymentProviderDescriptor.ATT_ID);
+ this.name = config.getAttribute(DeploymentProviderDescriptor.ATT_NAME);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
+ */
+ public Object getAdapter(Class adapter) {
+ return null;
+ }
+
+ /**
+ * Returns whether a resource can be mapped to multiple deployment providers
+ * of this type. Even if this method returns <code>false</code>, a resource can
+ * still be mapped to multiple providers whose id differs. By default,
+ * multiple mappings are not supported. Subclasses must override this method
+ * to change this behavior.
+ * @return whether multiple mappings to providers of this type are supported
+ */
+ public boolean isMultipleMappingsSupported() {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/IDeploymentProviderManager.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/IDeploymentProviderManager.java
new file mode 100644
index 000000000..6591e1c46
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/IDeploymentProviderManager.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * 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.team.core;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+
+/**
+ * Manages deployment providers. Clients can programatically map and unmap deployment
+ * providers to containers.
+ * <p>
+ * Clients are not intended to implement this interface.
+ * </p>
+ * @see RepositoryProvider
+ * @see DeploymentProvider
+ * @since 3.0
+ */
+public interface IDeploymentProviderManager {
+ /**
+ * Maps a container to the the given provider. Mappings are persisted across
+ * workbench sessions.
+ *
+ * @param container the container to be mapped to the given provider
+ * @param provider the provider to be mapped to the container
+ * @throws TeamException
+ */
+ public void map(IContainer container, DeploymentProvider provider) throws TeamException;
+
+ /**
+ * Unmaps the given provider from the container.
+ *
+ * @param container the container to be unmapped from the given provider
+ * @param provider the provider to unmap from the container
+ * @throws TeamException
+ */
+ public void unmap(IContainer container, DeploymentProvider provider) throws TeamException;
+
+ /**
+ * Returns the providers associated with the given resource. This loads the providers
+ * if not already loaded, and can be long running. To check the existance of
+ * a particular mapping call {@link #getMappedTo(IResource, String)} instead.
+ * This method returns an empty array if there are no mappings.
+ *
+ * @param resource the resource whose mappings are to be retreived
+ * @return the mappings for the resource
+ */
+ public DeploymentProvider[] getMappings(IResource resource);
+
+ /**
+ * Returns the providers with the given id associated with the given resource.
+ * This loads the providers if not already loaded, and can be long running.
+ * To check the existance of a particular mapping call
+ * {@link #getMappedTo(IResource, String)} instead.
+ * This method returns an empty array if there are no mappings. This method will
+ * only return either an empty array or an array of length 1 if the provider
+ * of the given type does not support multiple mappings
+ * (@see DeploymentProvider#isMultipleMappingsSupported()).
+ * @param resource the resource whose mappings are to be retreived
+ * @param id the id of the provider
+ * @return the mappings for the resource
+ */
+ public DeploymentProvider[] getMappings(IResource resource, String id);
+
+ /**
+ * Returns <code>true</code> if the resource is mapped to the provider with
+ * the given id, and <code>false</code> otherwise. This method is fast running
+ * and won't load the provider.
+ *
+ * @param resource the resource for which to check the mapping
+ * @param id the id of the provider
+ * @return <code>true</code> if the resource is mapped to the provider with
+ * the given id, and <code>false</code> otherwise.
+ */
+ public boolean getMappedTo(IResource resource, String id);
+
+ /**
+ * Return an array of all the resource roots that are mapped
+ * to a deployment providers with the given
+ * id. If id is <code>null</code>, the roots of all deployment providers
+ * are returned.
+ * @param id a deployment provider id or <code>null</code>
+ * @return all roots that are mapped to deployment providers with
+ * the given id
+ */
+ public IResource[] getDeploymentProviderRoots(String id);
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/ITeamStatus.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/ITeamStatus.java
new file mode 100644
index 000000000..d1ed783e3
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/ITeamStatus.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.team.core;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * Defines the status codes used in the status of exceptions and errors relating to Team.
+ */
+public interface ITeamStatus extends IStatus {
+
+ /**
+ * An error occurred trying to obtain the <code>SyncInfo</code> for a single resource.
+ * The error will be cleared when the set is reset or when a sync info is added to
+ * the set for the resource for which the error occurred.
+ */
+ public static final int RESOURCE_SYNC_INFO_ERROR = 1;
+
+ /**
+ * An error occurred that may effect several resources in a <code>SyncInfoSet</code>.
+ * The error will be cleared when the set is reset.
+ */
+ public static final int SYNC_INFO_SET_ERROR = 2;
+
+ /**
+ * Return the resource associated with this status.
+ * @return Returns the resource.
+ */
+ public IResource getResource();
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/ProjectSetCapability.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/ProjectSetCapability.java
index ef43f0b66..b8a66559e 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/ProjectSetCapability.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/ProjectSetCapability.java
@@ -15,31 +15,32 @@ import java.io.File;
import org.eclipse.core.runtime.IProgressMonitor;
/**
- * This class represents provisional API. A provider is not required to implement this API.
- * Implementers, and those who reference it, do so with the awareness that this class may be
- * removed or substantially changed at future times without warning.
+ * This class represents provisional API. A provider is not required to
+ * implement this API. Implementers, and those who reference it, do so with the
+ * awareness that this class may be removed or substantially changed at future
+ * times without warning.
* <p>
* The intention is that this class will eventually replace <code>IProjectSetSerializer</code>.
- * At the current time it only complements this API by providing a notification mechanism
- * for informing repository providers when a project set has been created.
+ * At the current time it only complements this API by providing a notification
+ * mechanism for informing repository providers when a project set has been
+ * created.
*
* @see IProjectSetSerializer
* @see RepositoryProviderType
*
* @since 2.1
*/
-
public abstract class ProjectSetCapability {
/**
- * Notify the provider that a project set has been created at path.
- * Only providers identified as having projects in the project set will be
- * notified. The project set may or may not be created in a workspace
+ * Notify the provider that a project set has been created at path. Only
+ * providers identified as having projects in the project set will be
+ * notified. The project set may or may not be created in a workspace
* project (thus may not be a resource).
*
- * @param File the project set file that was created
- */
+ * @param File
+ * the project set file that was created
+ */
public void projectSetCreated(File file, Object context, IProgressMonitor monitor) {
//default is to do nothing
}
-
}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/RepositoryProvider.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/RepositoryProvider.java
index 9482ae5f2..ec686faa9 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/RepositoryProvider.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/RepositoryProvider.java
@@ -18,7 +18,6 @@ import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.team.internal.core.Policy;
import org.eclipse.team.internal.core.TeamPlugin;
-import org.eclipse.team.internal.core.simpleAccess.SimpleAccessOperations;
/**
* A concrete subclass of <code>RepositoryProvider</code> is created for each
@@ -49,7 +48,7 @@ import org.eclipse.team.internal.core.simpleAccess.SimpleAccessOperations;
*
* @since 2.0
*/
-public abstract class RepositoryProvider implements IProjectNature {
+public abstract class RepositoryProvider implements IProjectNature, IAdaptable {
private final static String TEAM_SETID = "org.eclipse.team.repository-provider"; //$NON-NLS-1$
@@ -516,24 +515,6 @@ public abstract class RepositoryProvider implements IProjectNature {
return false;
}
}
-
- /**
- * Provisional non-API method.
- *
- * This method is here to allow experimentation with 3rd party tools
- * calling providers in a repository neutral manner.
- *
- * Returns an object which implements a set of provider neutral operations for this
- * provider. Answers <code>null</code> if the provider does not wish to support these
- * operations.
- *
- * @return the repository operations or <code>null</code> if the provider does not
- * support provider neutral operations.
- * @see SimpleAccessOperations
- */
- public SimpleAccessOperations getSimpleAccess() {
- return null;
- }
/*
* @see IProjectNature#getProject()
@@ -598,27 +579,6 @@ public abstract class RepositoryProvider implements IProjectNature {
}
/**
- * Convert a project that are using natures to mark them as Team projects
- * to instead use persistent properties. Optionally remove the nature from the project.
- * Do nothing if the project has no Team nature.
- * Assume that the same ID is used for both the nature and the persistent property.
- *
- * @deprecated
- */
- public static void convertNatureToProperty(IProject project, boolean removeNature) throws TeamException {
- RepositoryProvider provider = RepositoryProvider.getProvider(project);
- if(provider == null)
- return;
-
- String providerId = provider.getID();
-
- RepositoryProvider.map(project, providerId);
- if(removeNature) {
- Team.removeNatureFromProject(project, providerId, new NullProgressMonitor());
- }
- }
-
- /**
* Method validateCreateLink is invoked by the Platform Core TeamHook when a
* linked resource is about to be added to the provider's project. It should
* not be called by other clients and it should not need to be overridden by
@@ -660,4 +620,12 @@ public abstract class RepositoryProvider implements IProjectNature {
public boolean canHandleLinkedResources() {
return false;
}
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
+ */
+ public Object getAdapter(Class adapter) {
+ return null;
+ }
}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/Team.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/Team.java
index c0336b5c9..cc8b7416b 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/Team.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/Team.java
@@ -17,7 +17,6 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -28,8 +27,6 @@ import java.util.TreeMap;
import java.util.Vector;
import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
@@ -41,11 +38,11 @@ import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Preferences;
import org.eclipse.core.runtime.Status;
+import org.eclipse.team.internal.core.DeploymentProviderManager;
import org.eclipse.team.internal.core.Policy;
import org.eclipse.team.internal.core.StringMatcher;
import org.eclipse.team.internal.core.TeamPlugin;
@@ -54,13 +51,15 @@ import org.eclipse.team.internal.core.TeamPlugin;
* The Team class provides a global point of reference for the global ignore set
* and the text/binary registry.
*
+ * WVCM: how does the IProjectSetSerializer fit into the 3.0 team api?
+ *
* @since 2.0
*/
public final class Team {
- public static final String PREF_TEAM_IGNORES = "ignore_files"; //$NON-NLS-1$
- public static final String PREF_TEAM_TYPES = "file_types"; //$NON-NLS-1$
- public static final String PREF_TEAM_SEPARATOR = "\n"; //$NON-NLS-1$
+ private static final String PREF_TEAM_IGNORES = "ignore_files"; //$NON-NLS-1$
+ private static final String PREF_TEAM_TYPES = "file_types"; //$NON-NLS-1$
+ private static final String PREF_TEAM_SEPARATOR = "\n"; //$NON-NLS-1$
public static final Status OK_STATUS = new Status(Status.OK, TeamPlugin.ID, Status.OK, Policy.bind("ok"), null); //$NON-NLS-1$
// File type constants
@@ -75,6 +74,9 @@ public final class Team {
private static SortedMap globalIgnore, pluginIgnore;
private static StringMatcher[] ignoreMatchers;
+ // Deployment provider manager
+ private static IDeploymentProviderManager deploymentManager;
+
private static class FileTypeInfo implements IFileTypeInfo {
private String extension;
private int type;
@@ -296,55 +298,6 @@ public final class Team {
TeamPlugin.getPlugin().getPluginPreferences().setValue(PREF_TEAM_IGNORES, buf.toString());
}
- /**
- * Utility method for removing a project nature from a project.
- *
- * @param proj the project to remove the nature from
- * @param natureId the nature id to remove
- * @param monitor a progress monitor to indicate the duration of the operation, or
- * <code>null</code> if progress reporting is not required.
- *
- * @deprecated
- */
- public static void removeNatureFromProject(IProject proj, String natureId, IProgressMonitor monitor) throws TeamException {
- try {
- IProjectDescription description = proj.getDescription();
- String[] prevNatures= description.getNatureIds();
- List newNatures = new ArrayList(Arrays.asList(prevNatures));
- newNatures.remove(natureId);
- description.setNatureIds((String[])newNatures.toArray(new String[newNatures.size()]));
- proj.setDescription(description, monitor);
- } catch(CoreException e) {
- throw wrapException(Policy.bind("manager.errorRemovingNature", proj.getName(), natureId), e); //$NON-NLS-1$
- }
- }
-
- /**
- * Utility method for adding a nature to a project.
- *
- * @param proj the project to add the nature
- * @param natureId the id of the nature to assign to the project
- * @param monitor a progress monitor to indicate the duration of the operation, or
- * <code>null</code> if progress reporting is not required.
- *
- * @exception TeamException if a problem occured setting the nature
- *
- * @deprecated
- */
- public static void addNatureToProject(IProject proj, String natureId, IProgressMonitor monitor) throws TeamException {
- try {
- IProjectDescription description = proj.getDescription();
- String[] prevNatures= description.getNatureIds();
- String[] newNatures= new String[prevNatures.length + 1];
- System.arraycopy(prevNatures, 0, newNatures, 0, prevNatures.length);
- newNatures[prevNatures.length]= natureId;
- description.setNatureIds(newNatures);
- proj.setDescription(description, monitor);
- } catch(CoreException e) {
- throw wrapException(Policy.bind("manager.errorSettingNature", proj.getName(), natureId), e); //$NON-NLS-1$
- }
- }
-
/*
* TEXT
*
@@ -580,6 +533,7 @@ public final class Team {
}
}
}, IResourceChangeEvent.POST_CHANGE);
+ ResourcesPlugin.getWorkspace().addResourceChangeListener((DeploymentProviderManager)getDeploymentManager(), IResourceChangeEvent.PRE_AUTO_BUILD);
}
/**
@@ -649,4 +603,17 @@ public final class Team {
initializePluginPatterns(pTypes, gTypes);
return getFileTypeInfo(gTypes);
}
+
+ /**
+ * Returns the deployment manager.
+ * @return the deployment manager
+ * @since 3.0
+ */
+ public static IDeploymentProviderManager getDeploymentManager() {
+ if(deploymentManager == null) {
+ deploymentManager = new DeploymentProviderManager();
+ }
+ return deploymentManager;
+ }
+
}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/TeamException.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/TeamException.java
index b30984675..7566126b3 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/TeamException.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/TeamException.java
@@ -10,6 +10,8 @@
*******************************************************************************/
package org.eclipse.team.core;
+import java.lang.reflect.InvocationTargetException;
+
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -55,7 +57,7 @@ public class TeamException extends CoreException {
super(status);
}
- public TeamException(String message, Exception e) {
+ public TeamException(String message, Throwable e) {
super(new Status(IStatus.ERROR, TeamPlugin.ID, 0, message, e));
}
@@ -78,4 +80,15 @@ public class TeamException extends CoreException {
}
return new TeamException(e);
}
+
+ /*
+ * Static helper methods for creating exceptions
+ */
+ public static TeamException asTeamException(InvocationTargetException e) {
+ Throwable target = e.getTargetException();
+ if (target instanceof TeamException) {
+ return (TeamException) target;
+ }
+ return new TeamException(new Status(IStatus.ERROR, TeamPlugin.ID, UNABLE, target.getMessage() != null ? target.getMessage() : "", target)); //$NON-NLS-1$
+ }
}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/TeamStatus.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/TeamStatus.java
new file mode 100644
index 000000000..d34799b10
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/TeamStatus.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * 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.team.core;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Status;
+
+public class TeamStatus extends Status implements ITeamStatus {
+
+ private IResource resource;
+
+ /**
+ * Create a new status object.
+ * @param severity the severity; one of <code>OK</code>,
+ * <code>ERROR</code>, <code>INFO</code>, or <code>WARNING</code>
+ * @param pluginId the unique identifier of the relevant plug-in
+ * @param code the plug-in-specific status code, or <code>OK</code>
+ * @param message a human-readable message, localized to the
+ * current locale
+ * @param exception a low-level exception, or <code>null</code> if not
+ * applicable
+ * @param resource the resource asociated with the exception
+ */
+ public TeamStatus(int severity, String pluginId, int code, String message, Throwable exception, IResource resource) {
+ super(severity, pluginId, code, message, exception);
+ if (resource == null) {
+ this.resource = ResourcesPlugin.getWorkspace().getRoot();
+ } else {
+ this.resource = resource;
+ }
+ }
+
+ /**
+ * Return the resource associated with this status.
+ * @return Returns the resource.
+ */
+ public IResource getResource() {
+ return resource;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/FilteredSyncInfoCollector.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/FilteredSyncInfoCollector.java
new file mode 100644
index 000000000..7430b54f0
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/FilteredSyncInfoCollector.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 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.team.core.subscribers;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.ITeamStatus;
+import org.eclipse.team.core.synchronize.*;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * Populates an output <code>SyncInfoSet</code> with the <code>SyncInfo</code> from an
+ * input <code>SyncInfoSet</code> which match a <code>SyncInfoFilter</code>. The collector
+ * also dynamically updates the output set in reaction to changes in the input set.
+ * <p>
+ * This class is not intended to be subclassed by clients
+ *
+ * @see SyncInfoSet
+ * @see SyncInfoFilter
+ *
+ * @since 3.0
+ */
+public final class FilteredSyncInfoCollector implements ISyncInfoSetChangeListener {
+
+ private SyncInfoSet inputSet;
+ private SyncInfoSet outputSet;
+ private SyncInfoFilter filter;
+
+ /**
+ * Create a filtered sync info collector that collects sync info from the input set.
+ * @param collector the collector that provides the source set
+ * @param inputSet the input set
+ * @param outputSet the output set
+ * @param filter the filter to be applied to the output set
+ */
+ public FilteredSyncInfoCollector(SyncInfoSet inputSet, SyncInfoSet outputSet, SyncInfoFilter filter) {
+ this.inputSet = inputSet;
+ this.outputSet = outputSet;
+ this.filter = filter;
+ }
+
+ /**
+ * Start the collector. After this method returns the output <code>SyncInfoSet</code>
+ * of the collector will be populated.
+ */
+ public void start(IProgressMonitor monitor) {
+ inputSet.connect(this, monitor);
+ }
+
+ /**
+ * Return the output <code>SyncInfoSet</code> that contains the filtered <code>SyncInfo</code>.
+ * @return the output <code>SyncInfoSet</code>
+ */
+ public SyncInfoSet getSyncInfoSet() {
+ return outputSet;
+ }
+
+ /**
+ * Return the filter used by this collector.
+ * @return the filter
+ */
+ public SyncInfoFilter getFilter() {
+ return filter;
+ }
+
+ /**
+ * Dispose of the collector. The collector cannot be restarted after it has been disposed.
+ */
+ public void dispose() {
+ if (inputSet == null) return;
+ inputSet.removeSyncSetChangedListener(this);
+ inputSet = null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener#syncInfoSetReset(org.eclipse.team.core.synchronize.SyncInfoSet, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void syncInfoSetReset(SyncInfoSet set, IProgressMonitor monitor) {
+ SyncInfoSet syncSet = getSyncInfoSet();
+ try {
+ syncSet.beginInput();
+ monitor.beginTask(null, 100);
+ syncSet.clear();
+ syncSetChanged(set.getSyncInfos(), Policy.subMonitorFor(monitor, 95));
+ } finally {
+ syncSet.endInput(Policy.subMonitorFor(monitor, 5));
+ monitor.done();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener#syncInfoChanged(org.eclipse.team.core.synchronize.ISyncInfoSetChangeEvent, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void syncInfoChanged(ISyncInfoSetChangeEvent event, IProgressMonitor monitor) {
+ SyncInfoSet syncSet = getSyncInfoSet();
+ try {
+ syncSet.beginInput();
+ monitor.beginTask(null, 105);
+ syncSetChanged(event.getChangedResources(), Policy.subMonitorFor(monitor, 50));
+ syncSetChanged(event.getAddedResources(), Policy.subMonitorFor(monitor, 50));
+ remove(event.getRemovedResources());
+ } finally {
+ syncSet.endInput(Policy.subMonitorFor(monitor, 5));
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener#syncInfoSetErrors(org.eclipse.team.core.synchronize.SyncInfoSet, org.eclipse.team.core.ITeamStatus[], org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void syncInfoSetErrors(SyncInfoSet set, ITeamStatus[] errors, IProgressMonitor monitor) {
+ SyncInfoSet syncSet = getSyncInfoSet();
+ try {
+ syncSet.beginInput();
+ for (int i = 0; i < errors.length; i++) {
+ ITeamStatus status = errors[i];
+ syncSet.addError(status);
+ }
+ } finally {
+ syncSet.endInput(monitor);
+ }
+ }
+
+ private void remove(IResource[] resources) {
+ for (int i = 0; i < resources.length; i++) {
+ remove(resources[i]);
+ }
+ }
+
+ private void remove(IResource resource) {
+ SyncInfoSet syncSet = getSyncInfoSet();
+ SyncInfo oldInfo = syncSet.getSyncInfo(resource);
+ if (oldInfo != null) {
+ syncSet.remove(resource);
+ }
+ }
+
+ private void syncSetChanged(SyncInfo[] infos, IProgressMonitor monitor) {
+ for (int i = 0; i < infos.length; i++) {
+ collect(infos[i], monitor);
+ }
+ }
+
+ private void collect(SyncInfo info, IProgressMonitor monitor) {
+ SyncInfoSet syncSet = getSyncInfoSet();
+ boolean isOutOfSync = filter.select(info, monitor);
+ SyncInfo oldInfo = syncSet.getSyncInfo(info.getLocal());
+ boolean wasOutOfSync = oldInfo != null;
+ if (isOutOfSync) {
+ syncSet.add(info);
+ } else if (wasOutOfSync) {
+ syncSet.remove(info.getLocal());
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ISubscriberChangeEvent.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ISubscriberChangeEvent.java
new file mode 100644
index 000000000..0f644794a
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ISubscriberChangeEvent.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.team.core.subscribers;
+import org.eclipse.core.resources.IResource;
+public interface ISubscriberChangeEvent {
+ /*====================================================================
+ * Constants defining the kinds of team changes to resources:
+ *====================================================================*/
+ /**
+ * Delta kind constant indicating that the resource has not been changed in any way
+ * @see IResourceDelta#getKind
+ */
+ public static final int NO_CHANGE = 0;
+ /**
+ * Delta kind constant (bit mask) indicating that the synchronization state of a resource has changed.
+ */
+ public static final int SYNC_CHANGED = 0x1;
+ /**
+ * Delta kind constant (bit mask) indicating that a team provider has been configured on the resource.
+ * @see IResourceDelta#getKind
+ */
+ public static final int ROOT_ADDED = 0x2;
+ /**
+ * Delta kind constant (bit mask) indicating that a team provider has been de-configured on the resource.
+ * @see IResourceDelta#getKind
+ */
+ public static final int ROOT_REMOVED = 0x4;
+ public abstract int getFlags();
+ public abstract IResource getResource();
+ public abstract Subscriber getSubscriber();
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ISubscriberChangeListener.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ISubscriberChangeListener.java
new file mode 100644
index 000000000..4360d33f3
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/ISubscriberChangeListener.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.team.core.subscribers;
+
+
+import java.util.EventListener;
+
+/**
+ * A resource state change listener is notified of changes to resources
+ * regarding their team state.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @see ITeamManager#addResourceStateChangeListener(IResourceStateChangeListener)
+ */
+public interface ISubscriberChangeListener extends EventListener{
+
+ /**
+ * Notifies this listener that some resources' team properties have
+ * changed. The changes have already happened. For example, a resource's
+ * base revision may have changed. The resource tree is open for modification
+ * when this method is invoked, so markers can be created, etc.
+ * <p>
+ * Note: This method is called by Team core; it is not intended to be
+ * called directly by clients.
+ * </p>
+ *
+ * @param deltas detailing the kinds of team changes
+ *
+ * [Note: The changed state event is purposely vague. For now it is only
+ * a hint to listeners that they should query the provider to determine the
+ * resources new sync info.]
+ */
+ public void subscriberResourceChanged(ISubscriberChangeEvent[] deltas);
+}
+
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/Subscriber.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/Subscriber.java
new file mode 100644
index 000000000..e26e172a7
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/Subscriber.java
@@ -0,0 +1,280 @@
+/*******************************************************************************
+ * 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.team.core.subscribers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.IResourceVariantComparator;
+import org.eclipse.team.core.synchronize.SyncInfo;
+
+/**
+ * A Subscriber provides synchronization between local resources and a
+ * remote location that is used to share those resources.
+ * <p>
+ * When queried for the <code>SyncInfo</code> corresponding to a local resource using
+ * <code>getSyncInfo(IResource)</code>, the subscriber should not contact the server.
+ * Server round trips should only occur within the <code>refresh<code>
+ * method of the subscriber. Consequently,
+ * the implementation of a subscriber must cache enough state information for a remote resource to calculate the
+ * synchronization state without contacting the server. During a refresh, the latest remote resource state
+ * information should be fetched and cached. For
+ * a subscriber that supports three-way compare, the refresh should also fetch the latest base state unless this is
+ * available by some other means (e.g. for some repository tools, the base state is persisted on disk with the
+ * local resources).
+ * </p>
+ * <p>
+ * After a refresh, the subscriber must notify any listeners of local resources whose corresponding remote resource
+ * or base resource changed. The subscriber does not need to notify listeners when the state changes due to a local
+ * modification since local changes are available through the <code>IResource</code> delta mechanism. However,
+ * the subscriber must
+ * cache enough information (e..g the local timestamp of when the file was in-sync with its corresponding remote
+ * resource)
+ * to determine if the file represents an outgoing change so that <code>SyncInfo</code> obtained
+ * after a delta will indicate that the file has an outgoing change. The subscriber must also notify listeners
+ * when roots and added
+ * or removed. For example, a subscriber for a repository provider would fire a root added event when a project
+ * was shared
+ * with a repository. No event is required when a root is deleted as this is available through the
+ * <code>IResource</code> delta mechanism. It is up to clients to requery the subscriber
+ * when the state of a resource changes locally by listening to IResource deltas.
+ * </p>
+ * <p>
+ * The remote and base states can also include the state for resources that do not exist locally (i.e outgoing deletions
+ * or incoming additions). When queried for the members of a local resource, the subscriber should include any children
+ * for which a remote exists even if the local does not.
+ *
+ */
+abstract public class Subscriber {
+
+ private List listeners = new ArrayList(1);
+
+ /**
+ * Return the name of this subscription, in a format that is
+ * suitable for display to an end user.
+ *
+ * @return String representing the name of this subscription.
+ */
+ abstract public String getName();
+
+ /**
+ * Returns <code>true</code> if this resource is supervised by this
+ * subscriber. A supervised resource is one for which this subscriber
+ * maintains the synchronization state. Supervised resources are the only
+ * resources returned when <code>members(IResource)</code> was invoked with the parent
+ * of the resource. Returns <code>false</code> in all
+ * other cases.
+ *
+ * @return <code>true</code> if this resource is supervised, and <code>false</code>
+ * otherwise
+ */
+ abstract public boolean isSupervised(IResource resource) throws TeamException;
+
+ /**
+ * Returns all non-transient member resources of the given resource. The
+ * result will include entries for resources that exist either in the
+ * workspace or are implicated in an incoming change. Returns an empty list
+ * if the given resource exists neither in the workspace nor in the
+ * corresponding team stream, or if the given resource is transient.
+ * <p>
+ * This is a fast operation; the repository is not contacted.
+ * </p>
+ * <p>
+ * [Issue1 : Is there any filtering on the members? Just the ones that
+ * changed in some way, or *every member*? ]
+ * </p>
+ *
+ * @param resource
+ * the resource
+ * @return a list of member resources
+ * @exception CoreException
+ * if this request fails. Reasons include:
+ */
+ abstract public IResource[] members(IResource resource) throws TeamException;
+
+ /**
+ * Returns the list of root resources this subscriber considers for
+ * synchronization. A client should call this method first then can safely
+ * call <code>members</code> to navigate the resources managed by this
+ * subscriber.
+ *
+ * @return a list of resources
+ * @throws TeamException
+ */
+ abstract public IResource[] roots();
+
+ /**
+ * Returns synchronization info for the given resource, or <code>null</code>
+ * if there is no synchronization info because the subscriber does not
+ * apply to this resource.
+ * <p>
+ * Note that sync info may be returned for non-existing or for resources
+ * which have no corresponding remote resource.
+ * </p>
+ * <p>
+ * This method may take some time; it depends on the comparison criteria
+ * that is used to calculate the synchronization state (e.g. using content
+ * or only timestamps).
+ * </p>
+ *
+ * @param resource
+ * the resource of interest
+ * @return sync info
+ */
+ abstract public SyncInfo getSyncInfo(IResource resource) throws TeamException;
+
+ /**
+ * Returns the comparison criteria that will be used by the sync info
+ * created by this subscriber.
+ */
+ abstract public IResourceVariantComparator getResourceComparator();
+
+ /**
+ * Refreshes the resource hierarchy from the given resources and their
+ * children (to the specified depth) from the corresponding resources in
+ * the remote location. Resources are ignored in the following cases:
+ * <ul>
+ * <li>if they do not exist either in the workspace or in the
+ * corresponding remote location</li>
+ * <li>if the given resource is not managed by this subscriber</li>
+ * <li>if the given resource is a closed project (they are ineligible for
+ * synchronization)</li>
+ * <p>
+ * Typical synchronization operations use the statuses computed by this
+ * method as the basis for determining what to do. It is possible for the
+ * actual sync status of the resource to have changed since the current
+ * local sync status was refreshed. Operations typically skip resources
+ * with stale sync information. The chances of stale information being used
+ * can be reduced by running this method (where feasible) before doing
+ * other operations. Note that this will of course affect performance.
+ * </p>
+ * <p>
+ * The depth parameter controls whether refreshing is performed on just the
+ * given resource (depth= <code>DEPTH_ZERO</code>), the resource and its
+ * children (depth= <code>DEPTH_ONE</code>), or recursively to the
+ * resource and all its descendents (depth= <code>DEPTH_INFINITE</code>).
+ * Use depth <code>DEPTH_ONE</code>, rather than depth <code>DEPTH_ZERO</code>,
+ * to ensure that new members of a project or folder are detected.
+ * </p>
+ * <p>
+ * This method might change resources; any changes will be reported in a
+ * subsequent resource change event indicating changes to server sync
+ * status.
+ * </p>
+ * <p>
+ * This method contacts the server and is therefore long-running; progress
+ * and cancellation are provided by the given progress monitor.
+ * </p>
+ *
+ * @param resources
+ * the resources
+ * @param depth
+ * valid values are <code>DEPTH_ZERO</code>,<code>DEPTH_ONE</code>,
+ * or <code>DEPTH_INFINITE</code>
+ * @param monitor
+ * progress monitor, or <code>null</code> if progress
+ * reporting and cancellation are not desired
+ * @return status with code <code>OK</code> if there were no problems;
+ * otherwise a description (possibly a multi-status) consisting of
+ * low-severity warnings or informational messages.
+ * @exception CoreException
+ * if this method fails. Reasons include:
+ * <ul>
+ * <li>The server could not be contacted.</li>
+ * </ul>
+ */
+ abstract public void refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * Adds a listener to this team subscriber. Has no effect if an identical
+ * listener is already registered.
+ * <p>
+ * Team resource change listeners are informed about state changes that
+ * affect the resources supervised by this subscriber.
+ * </p>
+ *
+ * @param listener
+ * a team resource change listener
+ */
+ public void addListener(ISubscriberChangeListener listener) {
+ synchronized (listeners) {
+ if (!listeners.contains(listener)) {
+ listeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Removes a listener previously registered with this team subscriber. Has
+ * no affect if an identical listener is not registered.
+ *
+ * @param listener
+ * a team resource change listener
+ */
+ public void removeListener(ISubscriberChangeListener listener) {
+ synchronized (listeners) {
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Return an array of all out-of-sync resources (getKind() != 0) that occur
+ * under the given resources to the specified depth. The purpose of this
+ * method is to provide subscribers a means of optimizing the determination
+ * of all out-of-sync out-of-sync descendants of a set of resources.
+ * <p>
+ * A return value of an empty array indicates that there are no out-of-sync
+ * resources supervised by the subscriber. A return of <code>null</code>
+ * indicates that the subscriber does not support this operation in an
+ * optimized fashion. In this case, the caller can determine the
+ * out-of-sync resources by traversing the resource structure form the
+ * roots of the subscriber (@see <code>getRoots()</code>).
+ * </p>
+ *
+ * @param resources
+ * @param depth
+ * @param monitor
+ * @return
+ */
+ public SyncInfo[] getAllOutOfSync(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException {
+ return null;
+ }
+
+ /**
+ * Fires a team resource change event to all registered listeners Only
+ * listeners registered at the time this method is called are notified.
+ * Listener notification makes use of an ISafeRunnable to ensure that
+ * client exceptions do not effect the notification to other clients.
+ */
+ protected void fireTeamResourceChange(final ISubscriberChangeEvent[] deltas) {
+ ISubscriberChangeListener[] allListeners;
+ // Copy the listener list so we're not calling client code while synchronized
+ synchronized (listeners) {
+ allListeners = (ISubscriberChangeListener[]) listeners.toArray(new ISubscriberChangeListener[listeners.size()]);
+ }
+ // Notify the listeners safely so all will receive notification
+ for (int i = 0; i < allListeners.length; i++) {
+ final ISubscriberChangeListener listener = allListeners[i];
+ Platform.run(new ISafeRunnable() {
+ public void handleException(Throwable exception) {
+ // don't log the exception....it is already being logged in
+ // Platform#run
+ }
+ public void run() throws Exception {
+ listener.subscriberResourceChanged(deltas);
+ }
+ });
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SubscriberChangeEvent.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SubscriberChangeEvent.java
new file mode 100644
index 000000000..13c0de9c4
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SubscriberChangeEvent.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.team.core.subscribers;
+
+import org.eclipse.core.resources.IResource;
+
+/**
+ * A concrete implementation of <code>ISubscriberChangeEvent</code> that can
+ * be used by clients.
+ *
+ * @see IResource
+ * @see ITeamProvider
+ */
+public class SubscriberChangeEvent implements ISubscriberChangeEvent {
+
+ private Subscriber subscriber;
+ private int flags;
+ private IResource resource;
+
+ public SubscriberChangeEvent(Subscriber subscriber, int flags, IResource resource) {
+ this.subscriber = subscriber;
+ this.flags = flags;
+ this.resource = resource;
+ }
+
+ public int getFlags() {
+ return flags;
+ }
+
+ public IResource getResource() {
+ return resource;
+ }
+
+ public Subscriber getSubscriber() {
+ return subscriber;
+ }
+
+ /**
+ * Returns an array of deltas for the resources with SubscriberChangeEvent.SYNC_CHANGED
+ * as the change type.
+ * @param resources the resources whose sync info has changed
+ * @return an array of events
+ */
+ public static SubscriberChangeEvent[] asSyncChangedDeltas(Subscriber subscriber, IResource[] resources) {
+ SubscriberChangeEvent[] deltas = new SubscriberChangeEvent[resources.length];
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ deltas[i] = new SubscriberChangeEvent(subscriber, ISubscriberChangeEvent.SYNC_CHANGED, resource);
+ }
+ return deltas;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SubscriberSyncInfoCollector.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SubscriberSyncInfoCollector.java
new file mode 100644
index 000000000..8597675c0
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/SubscriberSyncInfoCollector.java
@@ -0,0 +1,384 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 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.team.core.subscribers;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.team.core.synchronize.*;
+import org.eclipse.team.internal.core.Assert;
+import org.eclipse.team.internal.core.subscribers.*;
+
+/**
+ * This collector maintains a {@link SyncInfoSet} for a particular team subscriber keeping
+ * it up-to-date with both incoming changes and outgoing changes as they occur for
+ * resources in the workspace. The collector can be configured to consider all the subscriber's
+ * roots or only a subset.
+ * <p>
+ * The advantage of this collector is that it processes both resource and team
+ * subscriber deltas in a background thread.
+ * </p>
+ * @since 3.0
+ */
+public final class SubscriberSyncInfoCollector implements IResourceChangeListener, ISubscriberChangeListener {
+
+ private SyncSetInputFromSubscriber subscriberInput;
+ private WorkingSetSyncSetInput workingSetInput;
+ private SyncSetInputFromSyncSet filteredInput;
+ private SubscriberEventHandler eventHandler;
+ private Subscriber subscriber;
+ private IResource[] roots;
+
+ /**
+ * Create a collector on the subscriber that collects out-of-sync resources
+ * for all roots of the subscriber. The <code>start()</code> method must be called after creation
+ * to prime the collector's sync sets.
+ * @param subscriber the Subscriber
+ */
+ public SubscriberSyncInfoCollector(Subscriber subscriber) {
+ this(subscriber, null /* use the subscriber roots */);
+ }
+
+ /**
+ * Create a collector that collects out-of-sync resources that are children of
+ * the given roots. If the roots are <code>null</code>, then all out-of-sync resources
+ * from the subscriber are collected. An empty array of roots will cause no resources
+ * to be collected. The <code>start()</code> method must be called after creation
+ * to rpime the collector's sync sets.
+ * @param subscriber the Subscriber
+ * @param roots the roots of the out-of-sync resources to be collected
+ */
+ public SubscriberSyncInfoCollector(Subscriber subscriber, IResource[] roots) {
+ this.roots = roots;
+ this.subscriber = subscriber;
+ Assert.isNotNull(subscriber);
+ this.eventHandler = new SubscriberEventHandler(subscriber);
+ this.subscriberInput = eventHandler.getSyncSetInput();
+ ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
+ subscriber.addListener(this);
+
+ // TODO: optimize and don't use working set if no roots are passed in
+ workingSetInput = new WorkingSetSyncSetInput(subscriberInput.getSyncSet(), getEventHandler());
+ filteredInput = new SyncSetInputFromSyncSet(workingSetInput.getSyncSet(), getEventHandler());
+ filteredInput.setFilter(new SyncInfoFilter() {
+ public boolean select(SyncInfo info, IProgressMonitor monitor) {
+ return true;
+ }
+ });
+ }
+
+ public void setProgressGroup(IProgressMonitor monitor, int ticks) {
+ getEventHandler().setProgressGroupHint(monitor, ticks);
+ }
+
+ /**
+ * Start the collector.
+ */
+ public void start() {
+ eventHandler.start();
+ }
+
+ /**
+ * Return the set that provides access to the out-of-sync resources for the collector's
+ * subscriber that are descendants of the roots for the collector,
+ * are in the collector's working set and match the collectors filter.
+ * @see getSubscriberSyncInfoSet()
+ * @see getWorkingSetSyncInfoSet()
+ * @return a SyncInfoSet containing out-of-sync resources
+ */
+ public SyncInfoTree getSyncInfoTree() {
+ return filteredInput.getSyncSet();
+ }
+
+ /**
+ * This causes the calling thread to wait any background collection of out-of-sync resources
+ * to stop before returning.
+ * @param monitor a progress monitor
+ */
+ public void waitForCollector(IProgressMonitor monitor) {
+ monitor.worked(1);
+ // wait for the event handler to process changes.
+ while(eventHandler.getEventHandlerJob().getState() != Job.NONE) {
+ monitor.worked(1);
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ }
+ }
+ monitor.worked(1);
+ }
+
+ /**
+ * Clears this collector's sync info sets and causes them to be recreated from the
+ * associated <code>Subscriber</code>. The reset will occur in the background. If the
+ * caller wishes to wait for the reset to complete, they should call
+ * {@link waitForCollector(IProgressMonitor)}.
+ */
+ public void reset() {
+ eventHandler.reset(getRoots());
+ }
+
+ /**
+ * Returns the <code>Subscriber</code> associated with this collector.
+ *
+ * @return the <code>Subscriber</code> associated with this collector.
+ */
+ public Subscriber getSubscriber() {
+ return subscriber;
+ }
+
+ /**
+ * Disposes of the background job associated with this collector and deregisters
+ * all it's listeners. This method must be called when the collector is no longer
+ * referenced and could be garbage collected.
+ */
+ public void dispose() {
+ eventHandler.shutdown();
+ subscriberInput.disconnect();
+ workingSetInput.disconnect();
+ if(filteredInput != null) {
+ filteredInput.disconnect();
+ }
+ getSubscriber().removeListener(this);
+ ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
+ }
+
+ /**
+ * Process the resource delta and posts all necessary events to the background
+ * event handler.
+ *
+ * @param delta the resource delta to analyse
+ */
+ private void processDelta(IResourceDelta delta) {
+ IResource resource = delta.getResource();
+ int kind = delta.getKind();
+
+ if (resource.getType() == IResource.PROJECT) {
+ // Handle a deleted project
+ if (((kind & IResourceDelta.REMOVED) != 0)) {
+ eventHandler.remove(resource);
+ return;
+ }
+ // Handle a closed project
+ if ((delta.getFlags() & IResourceDelta.OPEN) != 0 && !((IProject) resource).isOpen()) {
+ eventHandler.remove(resource);
+ return;
+ }
+ // Only interested in projects mapped to the provider
+ if (!isAncestorOfRoot(resource)) {
+ // If the project has any entries in the sync set, remove them
+ if (getSyncInfoTree().hasMembers(resource)) {
+ eventHandler.remove(resource);
+ }
+ return;
+ }
+ }
+
+ boolean visitChildren = false;
+ if (isDescendantOfRoot(resource)) {
+ visitChildren = true;
+ // If the resource has changed type, remove the old resource handle
+ // and add the new one
+ if ((delta.getFlags() & IResourceDelta.TYPE) != 0) {
+ eventHandler.remove(resource);
+ eventHandler.change(resource, IResource.DEPTH_INFINITE);
+ }
+
+ // Check the flags for changes the SyncSet cares about.
+ // Notice we don't care about MARKERS currently.
+ int changeFlags = delta.getFlags();
+ if ((changeFlags & (IResourceDelta.OPEN | IResourceDelta.CONTENT)) != 0) {
+ eventHandler.change(resource, IResource.DEPTH_ZERO);
+ }
+
+ // Check the kind and deal with those we care about
+ if ((delta.getKind() & (IResourceDelta.REMOVED | IResourceDelta.ADDED)) != 0) {
+ eventHandler.change(resource, IResource.DEPTH_ZERO);
+ }
+ }
+
+ // Handle changed children
+ if (visitChildren || isAncestorOfRoot(resource)) {
+ IResourceDelta[] affectedChildren = delta.getAffectedChildren(IResourceDelta.CHANGED | IResourceDelta.REMOVED | IResourceDelta.ADDED);
+ for (int i = 0; i < affectedChildren.length; i++) {
+ processDelta(affectedChildren[i]);
+ }
+ }
+ }
+
+ private boolean isAncestorOfRoot(IResource parent) {
+ // Always traverse into projects in case a root was removed
+ if (parent.getType() == IResource.ROOT) return true;
+ IResource[] roots = getRoots();
+ for (int i = 0; i < roots.length; i++) {
+ IResource resource = roots[i];
+ if (parent.getFullPath().isPrefixOf(resource.getFullPath())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isDescendantOfRoot(IResource resource) {
+ IResource[] roots = getRoots();
+ for (int i = 0; i < roots.length; i++) {
+ IResource root = roots[i];
+ if (root.getFullPath().isPrefixOf(resource.getFullPath())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the roots that are being considered by this collector.
+ * By default, the collector is interested in the roots of its
+ * subscriber. However, the set can be reduced using {@link setRoots(IResource)).
+ * @return
+ */
+ public IResource[] getRoots() {
+ if (roots == null) {
+ return getSubscriber().roots();
+ } else {
+ return roots;
+ }
+ }
+
+ /*
+ * Returns whether the collector is configured to collect for
+ * all roots of the subscriber or not
+ * @return <code>true</code> if the collector is considering all
+ * roots of the subscriber and <code>false</code> otherwise
+ */
+ private boolean isAllRootsIncluded() {
+ return roots == null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
+ */
+ public void resourceChanged(IResourceChangeEvent event) {
+ processDelta(event.getDelta());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.team.core.sync.ITeamResourceChangeListener#teamResourceChanged(org.eclipse.team.core.sync.TeamDelta[])
+ */
+ public void subscriberResourceChanged(ISubscriberChangeEvent[] deltas) {
+ for (int i = 0; i < deltas.length; i++) {
+ switch (deltas[i].getFlags()) {
+ case ISubscriberChangeEvent.SYNC_CHANGED :
+ if (isAllRootsIncluded() || isDescendantOfRoot(deltas[i].getResource())) {
+ eventHandler.change(deltas[i].getResource(), IResource.DEPTH_ZERO);
+ }
+ break;
+ case ISubscriberChangeEvent.ROOT_REMOVED :
+ eventHandler.remove(deltas[i].getResource());
+ break;
+ case ISubscriberChangeEvent.ROOT_ADDED :
+ if (isAllRootsIncluded() || isDescendantOfRoot(deltas[i].getResource())) {
+ eventHandler.change(deltas[i].getResource(), IResource.DEPTH_INFINITE);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Set the roots that are to be considered by the collector. The provided
+ * resources should be either a subset of the roots of the collector's subscriber
+ * or children of those roots. Other resources can be provided but will be ignored.
+ * Setting the roots to <code>null</code> will cause the roots of the subscriber
+ * to be used
+ * @param roots The roots to be considered or <code>null</code>.
+ */
+ public void setRoots(IResource[] roots) {
+ this.roots = roots;
+ }
+
+ /**
+ * Return the event handler that performs the background processing for this collector.
+ * The event handler also serves the purpose of serializing the modifications and adjustments
+ * to the collector's sync sets in order to ensure that the state of the sets is kept
+ * consistent.
+ * @return Returns the eventHandler.
+ */
+ protected SubscriberEventHandler getEventHandler() {
+ return eventHandler;
+ }
+
+ /**
+ * Set the working set resources used to filter the output <code>SyncInfoSet</code>.
+ * @see getWorkingSetSyncInfoSet()
+ * @param resources the working set resources
+ */
+ public void setWorkingSet(IResource[] resources) {
+ workingSetInput.setWorkingSet(resources);
+ workingSetInput.reset();
+ }
+
+ /**
+ * Get th working set resources used to filter the output sync info set.
+ * @return the working set resources
+ */
+ public IResource[] getWorkingSet() {
+ return workingSetInput.getWorkingSet();
+ }
+
+ /**
+ * Set the filter for this collector. Only elements that match the filter will
+ * be in the out sync info set.
+ * @see getSyncInfoSet()
+ * @param filter the sync info filter
+ */
+ public void setFilter(SyncInfoFilter filter) {
+ filteredInput.setFilter(filter);
+ filteredInput.reset();
+ }
+
+ /**
+ * Return the filter that is filtering the output of this collector.
+ * @return a sync info filter
+ */
+ public SyncInfoFilter getFilter() {
+ if(filteredInput != null) {
+ return filteredInput.getFilter();
+ }
+ return null;
+ }
+
+ /**
+ * Return a <code>SyncInfoSet</code> that contains the out-of-sync elements
+ * from the subsciber sync info set filtered
+ * by the working set resources but not the collector's <code>SyncInfoFilter</code>.
+ * @see getSubscriberSyncInfoSet()
+ * @return a <code>SyncInfoSet</code>
+ */
+ public SyncInfoSet getWorkingSetSyncInfoSet() {
+ return workingSetInput.getSyncSet();
+ }
+
+ /**
+ * Return the <code>SyncInfoSet</code> that contains all the all the out-of-sync resources for the
+ * subscriber that are descendants of the roots of this collector. The set will contain only those resources that are children of the roots
+ * of the collector unless the roots of the colletor has been set to <code>null</code>
+ * in which case all out-of-sync resources from the subscriber are collected.
+ * @return the subscriber sync info set
+ */
+ public SyncInfoSet getSubscriberSyncInfoSet() {
+ return subscriberInput.getSyncSet();
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/package.html b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/package.html
new file mode 100644
index 000000000..e590138cb
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/subscribers/package.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <meta content="text/html; charset=iso-8859-1"
+ http-equiv="Content-Type">
+ <meta content="IBM" name="Author">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Application programming interfaces for generating and refreshing
+synchronization state.
+<h2>Package Specification</h2>
+<p>This package specifies the API for Team subscribers. A Subscriber
+provides access to the synchronization state between the local
+workspace resources and a set of variants of those resources, whether
+it be a code repository or some other type of server (e.g. FTP). A
+subscriber is typically associated with only a subset of the resources
+in the local workspace, referred to as the set of resources the
+subscriber supervises. The supervised local resources have a
+corresponding variant state which describes the state of the remote
+resources that correspond to the local resources.</p>
+<p>A Subscriber provides:</p>
+<ul>
+ <li>a set of root resources that define the subset of resources in
+the workspace that the subscriber supervises (some children of the
+roots may not be supervised, as indicated by the isSupervised method).</li>
+ <li>access to the synchronization state (using SyncInfo) between the
+resources it supervises and their corresponding variant resources.</li>
+ <li> the ability to refresh the the remote state</li>
+ <li>change notification to registered listeners (of type
+ISubscriberChangeListener) when the variant state changes or when roots
+are added or removed.</li>
+</ul>
+<h3>Implementing a Subscriber </h3>
+<p>An implementation of a subscriber must provide:
+</p>
+<ul>
+ <li>a subclass of Subcriber which maintains the synchronization state
+between its local resources and their corresponding variants.</li>
+ <li>an implemenation of IResourceVariant which provides access to the
+contents and other state of a variant resource that corresponds to a
+local resource</li>
+ <li>an implementation of IResourceVariantComparator which is used by
+SyncInfo to determine the synchronization state of a resource.</li>
+</ul>
+Optionally, a subscriber may provide a subclass of SyncInfo in order to
+customize the algorithm used to
+determine the synchronization state of a resource.
+<p></p>
+<h3>Additional Classes</h3>
+<p>The SubscriberSyncInfoCollector is used to collect the SyncInfo for
+out-of-sync resources from a subscriber into a SyncInfoTree that can be
+used to, for example, display the out-of-sync resources of a subscriber
+to the user. The collector supports filtering by working set and
+SyncInfoFilter (e.g. to select groupings such as incoming-changes or
+conflicts). Even when filtered, the collector still maintains a set of
+all out-of-sync resources from the subscriber in order to provide
+statistics on the number of pre and post filtered out-of-sync resources.</p>
+<p>FilteredSyncInfoCollector: should be generalized</p>
+</body>
+</html>
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/CachedResourceVariant.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/CachedResourceVariant.java
new file mode 100644
index 000000000..57c8a538d
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/CachedResourceVariant.java
@@ -0,0 +1,232 @@
+/*******************************************************************************
+ * 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.team.core.synchronize;
+
+import java.io.InputStream;
+
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.core.*;
+import org.eclipse.team.internal.core.*;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * A resource variant is a partial implementation of a remote resource
+ * whose contents and handle are cached locally. It is assumed that a
+ * resource varant is an immutable version or revision of a resource.
+ * Therefore, once the contents are cached they cannot be replaced.
+ * However, the cached handle can be replaced to allow clients to
+ * cache addition state or properties for a resource variant.
+ * <p>
+ * Overriding subclasses need to provide a cache Id for al there resource variants
+ * and a cache path for each resource variant that uniquely identifies it. In addition,
+ * they must implement <code>fetchContents</code> to retrieve the contents of the
+ * resource variant and then call <code>setContents</code> to place these contents in the cache.
+ * Subclasses may also call <code>cacheHandle</code> in order to place the handle in the
+ * cache so that it can be retrieved later by calling <code>getCachedHandle</code> on any
+ * resource variant whose cache path is the same as the cached handle. This allows subclasses to
+ * cache additional resource variant properties such as author, comment, etc.
+ * </p>
+ * <p>
+ * The cache in which the resource variants reside will occasionally clear
+ * cached entries if they have not been accessed for a certain amount of time.
+ * </p>
+ */
+public abstract class CachedResourceVariant extends PlatformObject implements IResourceVariant {
+
+ // holds the storage instance for this resource variant
+ private IStorage storage;
+
+ /*
+ * Internal class which provides access to the cached contents
+ * of this resource variant
+ */
+ class ResourceVariantStorage implements IStorage {
+ public InputStream getContents() throws CoreException {
+ if (!isContentsCached()) {
+ // The cache may have been cleared if someone held
+ // on to the storage too long
+ throw new TeamException(Policy.bind("CachedResourceVariant.0", getCachePath())); //$NON-NLS-1$
+ }
+ return getCachedContents();
+ }
+ public IPath getFullPath() {
+ return getFullPath();
+ }
+ public String getName() {
+ return CachedResourceVariant.this.getName();
+ }
+ public boolean isReadOnly() {
+ return true;
+ }
+ public Object getAdapter(Class adapter) {
+ return CachedResourceVariant.this.getAdapter(adapter);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.synchronize.IRemoteResource#getStorage(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public IStorage getStorage(IProgressMonitor monitor) throws TeamException {
+ if (isContainer()) return null;
+ ensureContentsCached(monitor);
+ if (storage == null) {
+ storage = new ResourceVariantStorage();
+ }
+ return storage;
+ }
+
+ private void ensureContentsCached(IProgressMonitor monitor) throws TeamException {
+ // Ensure that the contents are cached from the server
+ if (!isContentsCached()) {
+ fetchContents(monitor);
+ }
+ }
+
+ /**
+ * Method that is invoked when the contents of the resource variant need to
+ * be fetched. This method will only be invoked for files (i.e.
+ * <code>isContainer()</code> returns <code>false</code>.
+ * Subclasses should override this method and invoke <code>setContents</code>
+ * with a stream containing the fetched contents.
+ * @param monitor a progress monitor
+ */
+ protected abstract void fetchContents(IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * This method should be invoked by subclasses from within their <code>fetchContents</code>
+ * method in order to cache the contents for this resource variant.
+ * @param stream the stream containing the contents of the resource variant
+ * @param monitor a progress monitor
+ * @throws TeamException
+ */
+ protected void setContents(InputStream stream, IProgressMonitor monitor) throws TeamException {
+ // Ensure that there is a cache entry to receive the contents
+ Assert.isTrue(!isContainer());
+ if (!isHandleCached()) cacheHandle();
+ getCacheEntry().setContents(stream, monitor);
+ }
+
+ private ResourceVariantCacheEntry getCacheEntry() {
+ return getCache().getCacheEntry(this.getCachePath());
+ }
+
+ /**
+ * Return whether there are already contents cached for this resource variant.
+ * This method will return <code>false</code> even if the contents are currently
+ * being cached by another thread. The consequence of this is that the contents
+ * may be fetched twice in the rare case where two threads request the same contents
+ * at the same time. For containers, this method will always return <code>false</code>.
+ */
+ protected boolean isContentsCached() {
+ if (isContainer() || !isHandleCached()) {
+ return false;
+ }
+ ResourceVariantCacheEntry entry = getCache().getCacheEntry(getCachePath());
+ return entry.getState() == ResourceVariantCacheEntry.READY;
+ }
+
+ /**
+ * Return the cached contents for this resource variant or <code>null</code>
+ * if the contents have not been cached.
+ * For containers, this method will always return <code>null</code>.
+ * @return the cached contents or <code>null</code>
+ * @throws TeamException
+ */
+ protected InputStream getCachedContents() throws TeamException {
+ if (isContainer() || !isContentsCached()) return null;
+ return getCache().getCacheEntry(getCachePath()).getContents();
+ }
+
+ /**
+ * Return <code>true</code> if the cache contains an entry for this resource
+ * variant. It is possible that another instance of this variant is cached.
+ * To get the cached instance, call <code>getCachedHandle()</code>. Note that
+ * cached contents can be retrieved from any handle to a resource variant whose
+ * cache path (as returned by <code>getCachePath()</code>) match but other
+ * state information may only be accessible from the cached copy.
+ * @return whether the variant is cached
+ */
+ protected boolean isHandleCached() {
+ return (getCache().hasEntry(getCachePath()));
+ }
+
+ /**
+ * Get the path that uniquely identifies the remote resource
+ * variant. This path descibes the remote location where
+ * the remote resource is stored and also uniquely identifies
+ * each resource variant. It is used to uniquely identify this
+ * resource variant when it is stored in the resource variant cache.
+ * @return the full path of the remote resource variant
+ */
+ protected abstract String getCachePath();
+
+ /**
+ * Return the size (in bytes) of the contents of this resource variant.
+ * The method will return 0 if the contents have not yet been cached
+ * locally.
+ * For containers, this method will always return 0.
+ */
+ public long getSize() {
+ if (isContainer() || !isContentsCached()) return 0;
+ ResourceVariantCacheEntry entry = getCacheEntry();
+ if (entry == null || entry.getState() != ResourceVariantCacheEntry.READY) {
+ return 0;
+ }
+ return entry.getSize();
+ }
+
+ /*
+ * Return the cache that is used to cache this resource variant and its contents.
+ * @return Returns the cache.
+ */
+ private ResourceVariantCache getCache() {
+ ResourceVariantCache.enableCaching(getCacheId());
+ return ResourceVariantCache.getCache(getCacheId());
+ }
+
+ /**
+ * Return the ID that uniquely identifies the cache in which this resource variant
+ * is to be cache. The ID of the plugin that provides the resource variant subclass
+ * is a good candidate for this ID. The creation, management and disposal of the cache
+ * is managed by Team.
+ * @return the cache ID
+ */
+ protected abstract String getCacheId();
+
+ /**
+ * Return the cached handle for this resource variant if there is
+ * one. If there isn't one, then <code>null</code> is returned.
+ * If there is no cached handle and one is desired, then <code>cacheHandle()</code>
+ * should be called.
+ * @return a cached copy of this resource variant or <code>null</code>
+ */
+ protected CachedResourceVariant getCachedHandle() {
+ ResourceVariantCacheEntry entry = getCacheEntry();
+ if (entry == null) return null;
+ return entry.getResourceVariant();
+ }
+
+ /**
+ * Cache this handle in the cache, replacing any previously cached handle.
+ * Note that caching this handle will replace any state associated with a
+ * previously cached handle, if there is one, but the contents will remain.
+ * The reason for this is the assumption that the cache path for a resource
+ * variant (as returned by <code>getCachePath()</code> identifies an immutable
+ * resource version (or revision). The ability to replace the handle itself
+ * is provided so that additional state may be cached before or after the contents
+ * are fetched.
+ */
+ protected void cacheHandle() {
+ getCache().add(getCachePath(), this);
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/FastSyncInfoFilter.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/FastSyncInfoFilter.java
new file mode 100644
index 000000000..19228a99d
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/FastSyncInfoFilter.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * 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.team.core.synchronize;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * A specialized <code>SyncInfoFilter</code> that does not require a progress monitor.
+ * This enables these filters to be used when determining menu enablement or other
+ * operations that must be short running.
+ *
+ * @see SyncInfo
+ * @see SyncInfoSet
+ * @see SyncInfoFilter
+ * @since 3.0
+ */
+public class FastSyncInfoFilter extends SyncInfoFilter {
+
+ /**
+ * Selects <code>SyncInfo</code> that match the given change type and direction.
+ * @param direction the change direction (<code>SyncInfo.OUTGOING</code>,
+ * <code>SyncInfo.INCOMING</code> and <code>SyncInfo.CONFLICTING</code>) that this filter matches
+ * @param change the change type (<code>SyncInfo.ADDITION</code>,
+ * <code>SyncInfo.DELETION</code> and <code>SyncInfo.CHANGE</code>) that this filter matches
+ * @return a <code>FastSyncInfoFilter</code> that selects <code>SyncInfo</code> that match the given
+ * change type and direction.
+ */
+ public static FastSyncInfoFilter getDirectionAndChangeFilter(int direction, int change) {
+ return new AndSyncInfoFilter(new FastSyncInfoFilter[]{new SyncInfoDirectionFilter(direction), new SyncInfoChangeTypeFilter(change)});
+ }
+
+ /**
+ * An abstract class whoich contains a set of <code>FastSyncInfoFilter</code> instances.
+ * Subclasses must provide the <code>select(SyncInfo)</code> method for determining
+ * matches.
+ */
+ public static abstract class CompoundSyncInfoFilter extends FastSyncInfoFilter {
+ protected FastSyncInfoFilter[] filters;
+ protected CompoundSyncInfoFilter(FastSyncInfoFilter[] filters) {
+ this.filters = filters;
+ }
+ }
+
+ /**
+ * Selects SyncInfo which match all child filters.
+ */
+ public static class AndSyncInfoFilter extends CompoundSyncInfoFilter {
+ public AndSyncInfoFilter(FastSyncInfoFilter[] filters) {
+ super(filters);
+ }
+ public boolean select(SyncInfo info) {
+ for (int i = 0; i < filters.length; i++) {
+ FastSyncInfoFilter filter = filters[i];
+ if (!filter.select(info)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ }
+
+ /**
+ * Selects <code>SyncInfo</code> instances that are auto-mergable.
+ */
+ public static class AutomergableFilter extends FastSyncInfoFilter {
+ public boolean select(SyncInfo info) {
+ return (info.getKind() & SyncInfo.AUTOMERGE_CONFLICT) != 0;
+ }
+ }
+
+ /**
+ * Selects <code>SyncInfo</code> instances that are pseudo-conflicts.
+ */
+
+ public static class PseudoConflictFilter extends FastSyncInfoFilter {
+ public boolean select(SyncInfo info) {
+ return info.getKind() != 0 && (info.getKind() & SyncInfo.PSEUDO_CONFLICT) == 0;
+ }
+ }
+
+ /**
+ * Selects <code>SyncInfo</code> that match any of the child filters.
+ */
+ public static class OrSyncInfoFilter extends CompoundSyncInfoFilter {
+ public OrSyncInfoFilter(FastSyncInfoFilter[] filters) {
+ super(filters);
+ }
+ public boolean select(SyncInfo info) {
+ for (int i = 0; i < filters.length; i++) {
+ FastSyncInfoFilter filter = filters[i];
+ if (filter.select(info)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Selects <code>SyncInfo</code> whose change type match those of the filter.
+ */
+ public static class SyncInfoChangeTypeFilter extends FastSyncInfoFilter {
+ private int[] changeFilters = new int[]{SyncInfo.ADDITION, SyncInfo.DELETION, SyncInfo.CHANGE};
+ /**
+ * Create a filter that will match <code>SyncInfo</code> whose change type
+ * match those passed as arguments to this constructor.
+ * @param changeFilters the array of change types (<code>SyncInfo.ADDITION</code>,
+ * <code>SyncInfo.DELETION</code> and <code>SyncInfo.CHANGE</code>) that this filter match
+ */
+ public SyncInfoChangeTypeFilter(int[] changeFilters) {
+ this.changeFilters = changeFilters;
+ }
+ /**
+ * Create a filter that will match <code>SyncInfo</code> whose change type
+ * match that passed as an argument to this constructor.
+ * @param change the change type (<code>SyncInfo.ADDITION</code>,
+ * <code>SyncInfo.DELETION</code> and <code>SyncInfo.CHANGE</code>) that this filter matches
+ */
+ public SyncInfoChangeTypeFilter(int change) {
+ this(new int[]{change});
+ }
+ public boolean select(SyncInfo info) {
+ int syncKind = info.getKind();
+ for (int i = 0; i < changeFilters.length; i++) {
+ int filter = changeFilters[i];
+ if ((syncKind & SyncInfo.CHANGE_MASK) == filter)
+ return true;
+ }
+ return false;
+ }
+ }
+ /**
+ * Selects <code>SyncInfo</code> whose change direction match those of the filter.
+ */
+ public static class SyncInfoDirectionFilter extends FastSyncInfoFilter {
+ int[] directionFilters = new int[] {SyncInfo.OUTGOING, SyncInfo.INCOMING, SyncInfo.CONFLICTING};
+ /**
+ * Create a filter that will match <code>SyncInfo</code> whose change direction
+ * match those passed as arguments to this constructor.
+ * @param directionFilters the array of change directions (<code>SyncInfo.OUTGOING</code>,
+ * <code>SyncInfo.INCOMING</code> and <code>SyncInfo.CONFLICTING</code>) that this filter match
+ */
+ public SyncInfoDirectionFilter(int[] directionFilters) {
+ this.directionFilters = directionFilters;
+ }
+ /**
+ * Create a filter that will match <code>SyncInfo</code> whose change direction
+ * match that passed as arguments to this constructor.
+ * @param direction the change direction (<code>SyncInfo.OUTGOING</code>,
+ * <code>SyncInfo.INCOMING</code> and <code>SyncInfo.CONFLICTING</code>) that this filter matches
+ */
+ public SyncInfoDirectionFilter(int direction) {
+ this(new int[] { direction });
+ }
+ public boolean select(SyncInfo info) {
+ int syncKind = info.getKind();
+ for (int i = 0; i < directionFilters.length; i++) {
+ int filter = directionFilters[i];
+ if ((syncKind & SyncInfo.DIRECTION_MASK) == filter)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Return true if the provided <code>SyncInfo</code> matches the filter. The default
+ * behavior it to include resources whose syncKind is non-zero.
+ *
+ * @param info the <code>SyncInfo</code> being tested
+ * @return <code>true</code> if the <code>SyncInfo</code> matches the filter
+ */
+ public boolean select(SyncInfo info) {
+ return info.getKind() != 0;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.subscribers.SyncInfoFilter#select(org.eclipse.team.core.subscribers.SyncInfo, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public final boolean select(SyncInfo info, IProgressMonitor monitor) {
+ return select(info);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/IResourceVariant.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/IResourceVariant.java
new file mode 100644
index 000000000..9b1039cbe
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/IResourceVariant.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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.team.core.synchronize;
+
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+
+/**
+ * This interface is used by <code>SyncInfo</code> instances
+ * to provide access to the base and remote resources that correspond to
+ * a local resource.
+ *
+ * @see SyncInfo
+ * @since 3.0
+ */
+public interface IResourceVariant {
+
+ /**
+ * Answers the name of the remote resource. The name may be
+ * displayed to the user.
+ *
+ * @return name of the subscriber resource.
+ */
+ public String getName();
+
+ /**
+ * Answers if the remote resource may have children.
+ *
+ * @return <code>true</code> if the remote element may have children and
+ * <code>false</code> otherwise.
+ */
+ public boolean isContainer();
+
+ /**
+ * Return an instance of IStorage or <code>null</code> if the remote resource
+ * does not have contents (i.e. is a folder). Since the <code>ISorage#getContents()</code>
+ * method does not accept an <code>IProgressMonitor</code>, this method must ensure that the contents
+ * access by the resulting <code>IStorage</code> is cached locally (hence the <code>IProgressMonitor</code>
+ * argument to this method). Implementations of this method should
+ * ensure that the resulting <code>IStorage</code> is accessing locally cached contents and is not
+ * contacting the server.
+ * @return an <code>IStorage</code> that provides access to the contents of
+ * the remote resource or <code>null</code> if the remote resource is a container.
+ */
+ public IStorage getStorage(IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * Return a content identifier that is used to differentiate versions
+ * or revisions of the same resource.
+ *
+ * @return a String that identifies the version of the subscriber resource
+ * @throws TeamException
+ */
+ public String getContentIdentifier();
+
+ /**
+ * Returns whether the remote resource is equal to the provided object.
+ * @param object the object to be compared
+ * @return whether the object is equal to the remote resource
+ */
+ public boolean equals(Object object);
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/IResourceVariantComparator.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/IResourceVariantComparator.java
new file mode 100644
index 000000000..58673c628
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/IResourceVariantComparator.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * 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.team.core.synchronize;
+
+import org.eclipse.core.resources.IResource;
+
+/**
+ * An <code>IResourceVariantComparator</code> is provided by a <code>Subscriber</code>
+ * and used by a <code>SyncInfo</code> to calculate the sync
+ * state of the workspace resources. Subscribers should provide a criteria
+ * best suited for their environment. For example, an FTP subscriber could choose to use file
+ * size or file timestamps as comparison criterias whereas a CVS workspace subscriber would
+ * use file revision numbers.
+ *
+ * @see SyncInfo
+ * @see Subscriber
+ * @since 3.0
+ */
+public interface IResourceVariantComparator {
+
+ /**
+ * Returns <code>true</code> if the local resource
+ * matches the remote resource based on this criteria and <code>false</code>
+ * otherwise. Comparing should be fast and based on cached information.
+ *
+ * @param resource the local resource to be compared
+ * @param remote the remote resources to be compared
+ * @return <code>true</code> if local and remote are equal based on this criteria and <code>false</code>
+ * otherwise.
+ */
+ public boolean compare(IResource local, IResourceVariant remote);
+
+ /**
+ * Returns <code>true</code> if the base resource
+ * matches the remote resource based on this criteria and <code>false</code>
+ * otherwise. Comparing should be fast and based on cached information.
+ *
+ * @param base the base resource to be compared
+ * @param remote the remote resources to be compared
+ * @return <code>true</code> if base and remote are equal based on this criteria and <code>false</code>
+ * otherwise.
+ */
+ public boolean compare(IResourceVariant base, IResourceVariant remote);
+
+ /**
+ * Answers <code>true</code> if the base tree is maintained by this
+ * subscriber. If the base tree is not considered than the subscriber can
+ * be considered as not supported three-way comparisons. Instead
+ * comparisons are made between the local and remote only without
+ * consideration for the base.
+ */
+ public boolean isThreeWay();
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoSetChangeEvent.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoSetChangeEvent.java
new file mode 100644
index 000000000..c37577479
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoSetChangeEvent.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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.team.core.synchronize;
+
+import org.eclipse.core.resources.IResource;
+
+/**
+ * An event generated when a {@link SyncInfoSet} collection is changed. The event contains
+ * a description of the changes which include added, changed and removed resources.
+ * In some cases, (e.g. when the change is too complicated to be efficiently described
+ * using the mechanisms provided by this interface) the event will be a reset. In these
+ * cases, the client should ignore any other contents of the event are reclaculate
+ * from scratch any state that is derived from the <code>SyncInfoSet</code> from
+ * which the event originated.
+ * <p>
+ * The mix of return types, SyncInfo and IResource, is a result of an optimization
+ * included in {@link SyncInfoSet} collections that doesn't maintain SyncInfo objects
+ * for in-sync resources.
+ *
+ * @see SyncInfoSet#addSyncSetChangedListener(ISyncInfoSetChangeListener)
+ * @see ISyncInfoSetChangeListener
+ * @since 3.0
+ */
+public interface ISyncInfoSetChangeEvent {
+
+ /**
+ * Returns newly added out-of-sync <code>SyncInfo</code> elements.
+ *
+ * @return newly added <code>SyncInfo</code> elements or an empty list if this event
+ * doesn't contain added resources.
+ */
+ public SyncInfo[] getAddedResources();
+
+ /**
+ * Returns changed <code>SyncInfo</code> elements. The returned elements
+ * are still out-of-sync.
+ *
+ * @return changed <code>SyncInfo</code> elements or an empty list if this event
+ * doesn't contain changes resources.
+ */
+ public SyncInfo[] getChangedResources();
+
+ /**
+ * Returns the removed <code>IResource</code> elements for which the set no longer
+ * contains on out-of-sync <code>SyncInfo</code>. The returned elements
+ * are all in-sync resources.
+ *
+ * @return removed <code>SyncInfo</code> elements or an empty list if this event
+ * doesn't contain removed resources.
+ */
+ public IResource[] getRemovedResources();
+
+ /**
+ * Returns the {@link SyncInfoSet} that generated these events.
+ *
+ * @return the {@link SyncInfoSet} that generated these events.
+ */
+ public SyncInfoSet getSet();
+
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoSetChangeListener.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoSetChangeListener.java
new file mode 100644
index 000000000..2fa4603ec
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoSetChangeListener.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * 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.team.core.synchronize;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.ITeamStatus;
+
+/**
+ * Classes which implement this interface provide methods that deal with the
+ * change events that are generated by a {@link SyncInfoSet}.
+ * <p>
+ * After creating an instance of a class that implements this interface it can
+ * be added to a sync info set using the <code>addSyncSetChangedListener</code>
+ * method and removed using the <code>removeSyncSetChangedListener</code>
+ * method.
+ * </p>
+ * The originating sync set holds modification locks on the sync info set to ensure
+ * that no more changes occur until after the current change event is processed.
+ * The implementors of this interface must not modify the set within the scope of
+ * the listener's methods. If modiciations are attempted a runtime exception will occur.
+ *
+ * @see ISyncInfoSetChangeEvent
+ * @since 3.0
+ */
+public interface ISyncInfoSetChangeListener {
+
+ /**
+ * Sent when the contents of a {@link SyncInfoSet} have been reset or the
+ * listener has been connected to the set for the first time using
+ * <code>SyncInfoSet#connect(ISyncInfoSetChangeListener, IProgressMonitor)</code>. Listeners
+ * should discard any state they have accumulated from the originating sync info set
+ * and re-obtain their state from the sync info set. The originating sync set will be
+ * locked for modification when this method is called.
+ * Clients should no modify the set within this method and other threads that try to
+ * modify the set will be blocked until the reset is processed.
+ * @param set the originating {@link SyncInfoSet}
+ */
+ public void syncInfoSetReset(SyncInfoSet set, IProgressMonitor monitor);
+
+ /**
+ * Sent when a {@link SyncInfoSet} changes. For example, when a resource's
+ * synchronization state changes. The originating sync set will be
+ * locked for modification when this method is called.
+ * Clients should no modify the set within this method and other threads that try to
+ * modify the set will be blocked until the change is processed.
+ * <p>
+ * If the originating set is an instance of <code>SyncInfoTree</code> then
+ * the event will be an instance of <code>iSyncInfoTreeChangeEvent</code>.
+ * Clients can determine this using an instancof check.
+ *
+ * @param event an event containing information about the change.
+ */
+ public void syncInfoChanged(ISyncInfoSetChangeEvent event, IProgressMonitor monitor);
+
+ /**
+ * This method is called when errors have occurred calculating the <code>SyncInfo</code>
+ * for a resource. The resource associated with the error is available from the
+ * <code>ITeamStatus</code>. This event only provides the latest errors that occurred.
+ * An array of all errors can be retrieved directly from the set.
+ * @param set the originating {@link SyncInfoSet}
+ * @param errors the errors that occurred during the latest set modifications
+ * @param monitor a progress monitor
+ */
+ public void syncInfoSetErrors(SyncInfoSet set, ITeamStatus[] errors, IProgressMonitor monitor);
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoTreeChangeEvent.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoTreeChangeEvent.java
new file mode 100644
index 000000000..d9a1940e8
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/ISyncInfoTreeChangeEvent.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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.team.core.synchronize;
+
+import org.eclipse.core.resources.IResource;
+
+/**
+ * This is a change event that provides access to changes in subtrees
+ * that contain the out-of-sync resources. It is the event type
+ * provided by {@link SyncInfoTree} when it notifies listeners
+ * of changes.
+ */
+public interface ISyncInfoTreeChangeEvent extends ISyncInfoSetChangeEvent {
+
+ /**
+ * Returns the highest parent resources of all newly added elements available in this event
+ * by calling <code>getAddedResources()</code>. In other words, it returns the set of all
+ * parent containers that did not previously have descendants in the sync set but are direct
+ * children of containers that did previously have descescendants in the set.
+ * <p>
+ * These roots are provided in order
+ * to allow listeners to optimize the reconciliation of hierachical views of
+ * the <code>SyncInfoSet</code> contents.
+ *
+ * @return parents of all newly added elements or an empty list if this event
+ * doesn't contain added resources.
+ */
+ public IResource[] getAddedSubtreeRoots();
+
+ /**
+ * Returns the highest parent resources of all newly removed elements available in this event
+ * by calling <code>getRemovedResources()</code>. In other words, it returns the set of all
+ * parent containers that previously had descendants in the sync set but are direct
+ * children of containers that still have descescendants in the set.
+ * <p>
+ * These roots are provided in order
+ * to allow listeners to optimize the reconciliation of hierachical views of
+ * the <code>SyncInfoSet</code> contents.
+ *
+ * @return parents of all newly removed elements. or an empty list if this event
+ * doesn't contain added resources.
+ */
+ public IResource[] getRemovedSubtreeRoots();
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfo.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfo.java
new file mode 100644
index 000000000..625c755bf
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfo.java
@@ -0,0 +1,411 @@
+/*******************************************************************************
+ * 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.team.core.synchronize;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.team.core.TeamException;
+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>
+ *
+ * @since 3.0
+ */
+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 IResourceVariant base;
+ private IResourceVariant remote;
+ private IResourceVariantComparator comparator;
+
+ private int syncKind;
+
+ /**
+ * Construct a sync info object.
+ */
+ public SyncInfo(IResource local, IResourceVariant base, IResourceVariant remote, IResourceVariantComparator comparator) {
+ this.local = local;
+ this.base = base;
+ this.remote = remote;
+ this.comparator = comparator;
+ }
+
+ /**
+ * 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 IResourceVariant 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 IResourceVariant getRemote() {
+ return remote;
+ }
+
+ /**
+ * Returns the subscriber that created and maintains this sync info
+ * object.
+ */
+ public IResourceVariantComparator getComparator() {
+ return comparator;
+ }
+
+ /**
+ * 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
+ IResourceVariant base1 = null;
+ if (node1.getBase() != null)
+ base1 = node1.getBase();
+ IResourceVariant base2 = null;
+ if (node2.getBase() != null)
+ base2 = node2.getBase();
+ if (!equalObjects(base1, base2)) return false;
+
+ // Finally, ensure the remote resources are equal
+ IResourceVariant remote1 = null;
+ if (node1.getRemote() != null)
+ remote1 = node1.getRemote();
+ IResourceVariant 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$
+ }
+
+ /**
+ * Method that is invoked after instance creation to initialize the sync kind.
+ * This method should only be invoked by the creator of the <code>SyncInfo</code>
+ * instance. It is not done from the constructor in order to allow subclasses
+ * to calculate the sync kind from any additional state variables they may have.
+ * @throws TeamException
+ */
+ public final void init() throws TeamException {
+ syncKind = calculateKind();
+ }
+
+ /**
+ * Method that is invoked from the <code>init()</code> method to calculate
+ * the sync kind for this instance of <code>SyncInfo</code>. The result is
+ * assigned to an instance variable and is available using <code>getKind()</code>.
+ * Subclasses should not invoke this method but may override it in order to customize
+ * the sync kind calculation algorithm.
+ * @return the sync kind of this <code>SyncInfo</code>
+ * @throws TeamException
+ */
+ protected int calculateKind() throws TeamException {
+ int description = IN_SYNC;
+
+ boolean localExists = local.exists();
+
+ if (comparator.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;
+ if (comparator.compare(local, remote)) {
+ description |= PSEUDO_CONFLICT;
+ }
+ }
+ }
+ } else {
+ if (!localExists) {
+ if (remote == null) {
+ description = CONFLICTING | DELETION | PSEUDO_CONFLICT;
+ } else {
+ if (comparator.compare(base, remote))
+ description = OUTGOING | DELETION;
+ else
+ description = CONFLICTING | CHANGE;
+ }
+ } else {
+ if (remote == null) {
+ if (comparator.compare(local, base))
+ description = INCOMING | DELETION;
+ else
+ description = CONFLICTING | CHANGE;
+ } else {
+ boolean ay = comparator.compare(local, base);
+ boolean am = comparator.compare(base, remote);
+ if (ay && am) {
+ // in-sync
+ } else if (ay && !am) {
+ description = INCOMING | CHANGE;
+ } else if (!ay && am) {
+ description = OUTGOING | CHANGE;
+ } else {
+ if(! comparator.compare(local, remote)) {
+ description = CONFLICTING | CHANGE;
+ }
+ }
+ }
+ }
+ }
+ } 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 (! comparator.compare(local, remote))
+ description= CHANGE;
+ }
+ }
+ }
+ return description;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoFilter.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoFilter.java
new file mode 100644
index 000000000..caa89b17e
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoFilter.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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.team.core.synchronize;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.internal.core.subscribers.*;
+
+/**
+ * A <code>SyncInfoFilter</code> tests a <code>SyncInfo</code> for inclusion,
+ * typically in a <code>SyncInfoSet</code>.
+ *
+ * @see SyncInfo
+ * @see SyncInfoSet
+ *
+ * @since 3.0
+ */
+public abstract class SyncInfoFilter {
+
+ /**
+ * Selects <code>SyncInfo</code> whose local and remote contents match.
+ * This filter makes use of the <code>IStorage</code> provided by
+ * an <code>IResourceVariant</code> to obtain the remote contents.
+ * This means that the comparison may contact the server unless the contents
+ * were cached locally by a previous operation. The caching of remote
+ * contents is subscriber specific.
+ * <p>
+ * For folders, the comparison always returns <code>true</code>.
+ */
+ public static class ContentComparisonSyncInfoFilter extends SyncInfoFilter {
+ ContentComparator criteria = new ContentComparator(false);
+ /**
+ * Create a filter that does not ignore whitespace.
+ */
+ public ContentComparisonSyncInfoFilter() {
+ this(false);
+ }
+ /**
+ * Create a filter and configure how whitspace is handled.
+ * @param ignoreWhitespace whether whitespace should be ignored
+ */
+ public ContentComparisonSyncInfoFilter(boolean ignoreWhitespace) {
+ criteria = new ContentComparator(ignoreWhitespace);
+ }
+ public boolean select(SyncInfo info, IProgressMonitor monitor) {
+ IResourceVariant remote = info.getRemote();
+ IResource local = info.getLocal();
+ if (local.getType() != IResource.FILE) return true;
+ if (remote == null) return !local.exists();
+ if (!local.exists()) return false;
+ return criteria.compare(local, remote, monitor);
+ }
+ }
+
+ /**
+ * Return <code>true</code> if the provided <code>SyncInfo</code> matches the filter.
+ *
+ * @param info the <code>SyncInfo</code> to be tested
+ * @param monitor a progress monitor
+ * @return <code>true</code> if the <code>SyncInfo</code> matches the filter
+ */
+ public abstract boolean select(SyncInfo info, IProgressMonitor monitor);
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoSet.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoSet.java
new file mode 100644
index 000000000..20b05e581
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoSet.java
@@ -0,0 +1,594 @@
+/*******************************************************************************
+ * 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.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.team.core.ITeamStatus;
+import org.eclipse.team.core.TeamStatus;
+import org.eclipse.team.core.synchronize.FastSyncInfoFilter.SyncInfoDirectionFilter;
+import org.eclipse.team.internal.core.*;
+import org.eclipse.team.internal.core.subscribers.SyncInfoStatistics;
+import org.eclipse.team.internal.core.subscribers.SyncSetChangedEvent;
+
+/**
+ * A dynamic collection of {@link SyncInfo} objects that provides
+ * change notification to registered listeners. Batching of change notifications
+ * can be accomplished using the <code>beginInput/endInput</code> methods or
+ * the <code>run</code> method.
+ * @see SyncInfoTree
+ * @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 <code>SyncInfoSet</code> containing the given <code>SyncInfo</code>
+ * instances
+ * @param infos the <code>SyncInfo</code> 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 <code>SyncInfo</code> for all out-of-sync resources that are contained by the set.
+ * This call is equivalent in function to
+ * <code>getSyncInfos(ResourcesPlugin.getWorkspace().getRoot(), IResource.DEPTH_INFINITE)</code>
+ * but is optimized to retrieve all contained <code>SyncInfo</code>.
+ * @return an array of <code>SyncInfo</code>
+ */
+ 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 <code>getSyncInfos()</code> 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 <code>SyncInfo</code> for the given resource or <code>null</code>
+ * if the resource is not contained in the set.
+ * @param resource the resource
+ * @return the <code>SyncInfo</code> for the resource or <code>null</code>
+ */
+ 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.
+ */
+ 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. <code>(SyncInfo#getKind() & mask) == kind</code>).
+ * @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 <code>true</code> if there are any conflicting nodes in the set, and
+ * <code>false</code> otherwise.
+ */
+ public boolean hasConflicts() {
+ return countFor(SyncInfo.CONFLICTING, SyncInfo.DIRECTION_MASK) > 0;
+ }
+
+ /**
+ * Return whether the set is empty.
+ * @return <code>true</code> if the set is empty
+ */
+ public synchronized boolean isEmpty() {
+ return resources.isEmpty();
+ }
+
+ /**
+ * Add the <code>SyncInfo</code> to the set, replacing any previously existing one.
+ * @param info the new <code>SyncInfo</code>
+ */
+ 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 <code>SyncInfo</code> 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);
+ }
+ }
+
+ /**
+ * Deregisters the given listener for participant notifications. Has
+ * no effect if listener is not already registered.
+ *
+ * @param listener listener to deregister
+ */
+ public void removeSyncSetChangedListener(ISyncInfoSetChangeListener listener) {
+ synchronized(listeners) {
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Reset the sync set so it is empty.
+ */
+ 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.
+ * <p>
+ * 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 gaurenteed that two invocations
+ * of <code>run</code> performed in the same thread will be executed in the
+ * same order even if run in different threads.
+ *
+ * @param runnable a runnable
+ * @param progress a progress monitor or <code>null</code>
+ */
+ 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 <code>addChangeListener</code>
+ * 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.
+ * <p>
+ * 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 <code>SyncInfoSet</code>
+ * implementation.
+ * <p>
+ * Disconnecting is done by calling <code>removeChangeListener</code>. Once disconnected,
+ * a listener can reconnect to be reinitialized.
+ * @param listener
+ * @param monitor
+ */
+ public void connect(final ISyncInfoSetChangeListener listener, IProgressMonitor monitor) {
+ run(new IWorkspaceRunnable() {
+ public void run(IProgressMonitor monitor) {
+ try {
+ monitor.beginTask(null, 100);
+ addSyncSetChangedListener(listener);
+ listener.syncInfoSetReset(SyncInfoSet.this, Policy.subMonitorFor(monitor, 95));
+ } finally {
+ monitor.done();
+ }
+ }
+ }, monitor);
+ }
+
+ private ILock lock = Platform.getJobManager().newLock();
+
+ private Set listeners = Collections.synchronizedSet(new HashSet());
+
+ private SyncSetChangedEvent changes = createEmptyChangeEvent();
+
+ /**
+ * Add the given <code>SyncInfo</code> to the set. A change event will
+ * be generated unless the call to this method is nested in between calls
+ * to <code>beginInput()</code> and <code>endInput(IProgressMonitor)</code>
+ * in which case the event for this addition and any other sync set
+ * change will be fired in a batched event when <code>endInput</code>
+ * is invoked.
+ * Invoking this method outside of the above mentioned block will result
+ * in the <code>endInput(IProgressMonitor)</code> being invoked with a null
+ * progress monitor. If responsiveness is required, the client should always
+ * nest sync set modifications.
+ * @param info
+ */
+ public void add(SyncInfo info) {
+ try {
+ beginInput();
+ boolean alreadyExists = getSyncInfo(info.getLocal()) != null;
+ internalAdd(info);
+ if (alreadyExists) {
+ getChangeEvent().changed(info);
+ } else {
+ getChangeEvent().added(info);
+ }
+ } finally {
+ endInput(null);
+ }
+ }
+
+ /**
+ * Add all the syncinfo from the given set to this set.
+ * @param set the set whose sync info should be added to this set
+ */
+ public void addAll(SyncInfoSet set) {
+ try {
+ beginInput();
+ SyncInfo[] infos = set.getSyncInfos();
+ for (int i = 0; i < infos.length; i++) {
+ add(infos[i]);
+ }
+ } finally {
+ endInput(null);
+ }
+ }
+
+ /**
+ * Remove the given local resource from the set
+ * @param resource the local resource to remove
+ */
+ public synchronized void remove(IResource resource) {
+ try {
+ beginInput();
+ SyncInfo info = internalRemove(resource);
+ getChangeEvent().removed(resource, info);
+ } finally {
+ endInput(null);
+ }
+ }
+
+ /**
+ * Remove all the given resources from the set.
+ * @param resources the resources to be removed
+ */
+ public void removeAll(IResource[] resources) {
+ try {
+ beginInput();
+ for (int i = 0; i < resources.length; i++) {
+ remove(resources[i]);
+ }
+ } finally {
+ endInput(null);
+ }
+ }
+
+ /**
+ * Removes all conflicting nodes from this set.
+ */
+ public void removeConflictingNodes() {
+ rejectNodes(new SyncInfoDirectionFilter(SyncInfo.CONFLICTING));
+ }
+
+ /**
+ * Removes all outgoing nodes from this set.
+ */
+ public void removeOutgoingNodes() {
+ rejectNodes(new SyncInfoDirectionFilter(SyncInfo.OUTGOING));
+ }
+
+ /**
+ * Removes all incoming nodes from this set.
+ */
+ public void removeIncomingNodes() {
+ rejectNodes(new SyncInfoDirectionFilter(SyncInfo.INCOMING));
+ }
+
+ /**
+ * Indicate whether the set has nodes matching the given filter.
+ * @param filter a sync info filter
+ */
+ public boolean hasNodes(FastSyncInfoFilter filter) {
+ SyncInfo[] infos = getSyncInfos();
+ for (int i = 0; i < infos.length; i++) {
+ SyncInfo info = infos[i];
+ if (info != null && filter.select(info)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes all nodes from this set that do not match the given filter
+ * leaving only those that do match the filter.
+ * @param filter a sync info filter
+ */
+ public void selectNodes(FastSyncInfoFilter filter) {
+ try {
+ beginInput();
+ SyncInfo[] infos = getSyncInfos();
+ for (int i = 0; i < infos.length; i++) {
+ SyncInfo info = infos[i];
+ if (info == null || !filter.select(info)) {
+ remove(info.getLocal());
+ }
+ }
+ } finally {
+ endInput(null);
+ }
+ }
+
+ /**
+ * Removes all nodes from this set that match the given filter
+ * leaving those that do not match the filter.
+ * @param filter a sync info filter
+ */
+ public void rejectNodes(FastSyncInfoFilter filter) {
+ try {
+ beginInput();
+ SyncInfo[] infos = getSyncInfos();
+ for (int i = 0; i < infos.length; i++) {
+ SyncInfo info = infos[i];
+ if (info != null && filter.select(info)) {
+ remove(info.getLocal());
+ }
+ }
+ } finally {
+ endInput(null);
+ }
+ }
+
+ /**
+ * Return all nodes in this set that match the given filter.
+ * @param filter a sync info filter
+ */
+ public SyncInfo[] getNodes(FastSyncInfoFilter filter) {
+ List result = new ArrayList();
+ SyncInfo[] infos = getSyncInfos();
+ for (int i = 0; i < infos.length; i++) {
+ SyncInfo info = infos[i];
+ if (info != null && filter.select(info)) {
+ result.add(info);
+ }
+ }
+ return (SyncInfo[]) result.toArray(new SyncInfo[result.size()]);
+ }
+
+ /**
+ * Returns <code>true</code> if this sync set has incoming changes.
+ * Note that conflicts are not considered to be incoming changes.
+ */
+ public boolean hasIncomingChanges() {
+ return countFor(SyncInfo.INCOMING, SyncInfo.DIRECTION_MASK) > 0;
+ }
+
+ /**
+ * Returns <code>true</code> if this sync set has outgoing changes.
+ * Note that conflicts are not considered to be 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 <code>org.eclipse.core.runtime.jobs.ILock</code>.
+ * <p>
+ * It is important that the lock is released after it is obtained. Calls to <code>endInput</code>
+ * should be done in a finally block as illustrated in the following code snippet.
+ * <pre>
+ * try {
+ * set.beginInput();
+ * // do stuff
+ * } finally {
+ * set.endInput(progress);
+ * }
+ * </pre>
+ * Calls to <code>beginInput</code> and <code>endInput</code> can be nested and must be matched.
+ */
+ public void beginInput() {
+ lock.acquire();
+ }
+
+ /**
+ * This method is used to release the lock on this set. The prgress monitor is needed to allow
+ * listeners to perform long-running operations is reponse to the set change. The lock is held
+ * while the listeners are notified so listeners must be cautious in order to avoid deadlock.
+ */
+ public void endInput(IProgressMonitor monitor) {
+ if (lock.getDepth() == 1) {
+ // Remain locked while firing the events so the handlers
+ // can expect the set to remain constant while they process the events
+ fireChanges(Policy.monitorFor(monitor));
+ }
+ lock.release();
+ }
+
+ protected void resetChanges() {
+ changes = createEmptyChangeEvent();
+ }
+
+ /**
+ * Create an empty change event. Subclass may override to provided specialized event types
+ * @return an empty change event
+ */
+ protected SyncSetChangedEvent createEmptyChangeEvent() {
+ return new SyncSetChangedEvent(this);
+ }
+
+ private void fireChanges(final IProgressMonitor monitor) {
+ // Use a synchronized block to ensure that the event we send is static
+ final SyncSetChangedEvent event;
+ synchronized(this) {
+ event = getChangeEvent();
+ resetChanges();
+ }
+ // Ensure that the list of listeners is not changed while events are fired.
+ // Copy the listeners so that addition/removal is not blocked by event listeners
+ if(event.isEmpty() && ! event.isReset()) return;
+ ISyncInfoSetChangeListener[] allListeners = getListeners();
+ // Fire the events using an ISafeRunnable
+ final ITeamStatus[] newErrors = event.getErrors();
+ monitor.beginTask(null, 100 + (newErrors.length > 0 ? 50 : 0) * allListeners.length);
+ for (int i = 0; i < allListeners.length; i++) {
+ final ISyncInfoSetChangeListener listener = allListeners[i];
+ Platform.run(new ISafeRunnable() {
+ public void handleException(Throwable exception) {
+ // don't log the exception....it is already being logged in Platform#run
+ }
+ public void run() throws Exception {
+ try {
+ lockedForModification = true;
+ if (event.isReset()) {
+ listener.syncInfoSetReset(SyncInfoSet.this, Policy.subMonitorFor(monitor, 100));
+ } else {
+ listener.syncInfoChanged(event, Policy.subMonitorFor(monitor, 100));
+ }
+ if (newErrors.length > 0) {
+ listener.syncInfoSetErrors(SyncInfoSet.this, newErrors, Policy.subMonitorFor(monitor, 50));
+ }
+ } finally {
+ lockedForModification = false;
+ }
+ }
+ });
+ }
+ monitor.done();
+ }
+
+ /**
+ * Return a copy of all the listeners registered with this set
+ * @return the listeners
+ */
+ protected ISyncInfoSetChangeListener[] getListeners() {
+ ISyncInfoSetChangeListener[] allListeners;
+ synchronized(listeners) {
+ allListeners = (ISyncInfoSetChangeListener[]) listeners.toArray(new ISyncInfoSetChangeListener[listeners.size()]);
+ }
+ return allListeners;
+ }
+
+ /**
+ * Return the change event that is accumulating the changes to the set.
+ * This can be called by sublasses to access the event.
+ * @return Returns the changes.
+ */
+ protected SyncSetChangedEvent getChangeEvent() {
+ return changes;
+ }
+
+ /**
+ * Add the error to the set. Errors should be added to the set when the client
+ * populating the set cannot determine the <code>SyncInfo</code> 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 <code>ITeamStatus</code>). It is up to the
+ * client populating the set to ensure that the error associated with a
+ * resource contains all relevent information.
+ * The error will remain in the set until the set is reset.
+ * @param resource the resource associated with the error or the workspace root
+ * @param status the status that describes the error that occurred.
+ */
+ public void addError(ITeamStatus status) {
+ try {
+ beginInput();
+ errors.put(status.getResource(), status);
+ getChangeEvent().errorOccurred(status);
+ } finally {
+ endInput(null);
+ }
+ }
+
+ /**
+ * Return an array of the errors the occurred while populating this set.
+ * The errors will remain with the set until it is reset.
+ * @return the errors
+ */
+ public ITeamStatus[] getErrors() {
+ return (ITeamStatus[]) errors.values().toArray(new ITeamStatus[errors.size()]);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoTree.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoTree.java
new file mode 100644
index 000000000..c56f7465d
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/SyncInfoTree.java
@@ -0,0 +1,348 @@
+/*******************************************************************************
+ * 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.team.core.synchronize;
+
+import java.util.*;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.internal.core.TeamPlugin;
+import org.eclipse.team.internal.core.subscribers.SyncInfoTreeChangeEvent;
+import org.eclipse.team.internal.core.subscribers.SyncSetChangedEvent;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * Provides addition API for accessing the <code>SyncInfo</code> in the set through
+ * their resource's hierarchical relationships.
+ * <p>
+ * Events fired from a <code>SyncInfoTree</code> will be instances of <code>ISyncInfoTreeChangeEvent</code>.
+ */
+public class SyncInfoTree extends SyncInfoSet {
+
+ protected Map parents = Collections.synchronizedMap(new HashMap());
+
+ /**
+ * Create an empty sync info tree.
+ */
+ public SyncInfoTree() {
+ super();
+ }
+
+ /**
+ * Create a sync info tree containing the given sync info elements.
+ * @param infos the sync info elements
+ */
+ public SyncInfoTree(SyncInfo[] infos) {
+ super(infos);
+ }
+
+ /**
+ * Return wether the given resource has any children in the sync set. The children
+ * could be either out-of-sync resources that are contained by the set or containers
+ * that are ancestors of out-of-sync resources contained by the set.
+ * @param resource the parent resource
+ * @return the members of the parent in the set.
+ */
+ public synchronized boolean hasMembers(IResource resource) {
+ if (resource.getType() == IResource.FILE) return false;
+ IContainer parent = (IContainer)resource;
+ if (parent.getType() == IResource.ROOT) return !isEmpty();
+ IPath path = parent.getFullPath();
+ Set allDescendants = (Set)parents.get(path);
+ return (allDescendants != null && !allDescendants.isEmpty());
+ }
+
+ /**
+ * Return the <code>SyncInfo</code> for each out-of-sync resource in the subtree rooted at the given resource
+ * to the depth specified. The depth is one of:
+ * <ul>
+ * <li><code>IResource.DEPTH_ZERO</code>: the resource only,
+ * <li><code>IResource.DEPTH_ONE</code>: the resource or its direct children,
+ * <li><code>IResource.DEPTH_INFINITE</code>: the resource and all of it's descendants.
+ * <ul>
+ * If the given resource is out of sync, it will be included in the result.
+ * <p>
+ * The default implementation makes use of <code>getSyncInfo(IResource)</code>,
+ * <code>members(IResource)</code> and <code>getSyncInfos()</code>
+ * to provide the varying depths. Subclasses may override to optimize.
+ *
+ * @param resource the root of the resource subtree
+ * @param depth the depth of the subtree
+ * @return the <code>SyncInfo</code> for any out-of-sync resources
+ */
+ public synchronized SyncInfo[] getSyncInfos(IResource resource, int depth) {
+ if (depth == IResource.DEPTH_ZERO || resource.getType() == IResource.FILE) {
+ SyncInfo info = getSyncInfo(resource);
+ if (info == null) {
+ return new SyncInfo[0];
+ } else {
+ return new SyncInfo[] { info };
+ }
+ }
+ if (depth == IResource.DEPTH_ONE) {
+ List result = new ArrayList();
+ SyncInfo info = getSyncInfo(resource);
+ if (info != null) {
+ result.add(info);
+ }
+ IResource[] members = members(resource);
+ for (int i = 0; i < members.length; i++) {
+ IResource member = members[i];
+ info = getSyncInfo(member);
+ if (info != null) {
+ result.add(info);
+ }
+ }
+ return (SyncInfo[]) result.toArray(new SyncInfo[result.size()]);
+ }
+ // if it's the root then return all out of sync resources.
+ if(resource.getType() == IResource.ROOT) {
+ return getSyncInfos();
+ }
+ // for folders return all children deep.
+ return internalGetDeepSyncInfo((IContainer)resource);
+ }
+
+ /*
+ * Return the <code>SyncInfo</code> for all out-of-sync resources in the
+ * set that are at or below the given resource in the resource hierarchy.
+ * @param resource the root resource
+ * @return the <code>SyncInfo</code> for all out-of-sync resources at or below the given resource
+ */
+ private synchronized SyncInfo[] internalGetDeepSyncInfo(IContainer resource) {
+ List infos = new ArrayList();
+ IResource[] children = internalGetOutOfSyncDescendants((IContainer)resource);
+ for (int i = 0; i < children.length; i++) {
+ IResource child = children[i];
+ SyncInfo info = getSyncInfo(child);
+ if(info != null) {
+ infos.add(info);
+ } else {
+ TeamPlugin.log(IStatus.INFO, Policy.bind("SyncInfoTree.0") + child.getFullPath(), null); //$NON-NLS-1$
+ }
+ }
+ return (SyncInfo[]) infos.toArray(new SyncInfo[infos.size()]);
+ }
+
+ protected SyncSetChangedEvent createEmptyChangeEvent() {
+ return new SyncInfoTreeChangeEvent(this);
+ }
+
+ /**
+ * Add the given <code>SyncInfo</code> to the set. A change event will
+ * be generated unless the call to this method is nested in between calls
+ * to <code>beginInput()</code> and <code>endInput(IProgressMonitor)</code>
+ * in which case the event for this addition and any other sync set
+ * change will be fired in a batched event when <code>endInput</code>
+ * is invoked.
+ * Invoking this method outside of the above mentioned block will result
+ * in the <code>endInput(IProgressMonitor)</code> being invoked with a null
+ * progress monitor. If responsiveness is required, the client should always
+ * nest sync set modifications.
+ * @param info
+ */
+ public void add(SyncInfo info) {
+ try {
+ beginInput();
+ super.add(info);
+ IResource local = info.getLocal();
+ addToParents(local, local);
+ } finally {
+ endInput(null);
+ }
+ }
+
+ /**
+ * Remove the <code>SyncInfo</code> for the given resource from this set.
+ * @param resource the resource to be removed
+ */
+ public void remove(IResource resource) {
+ try {
+ beginInput();
+ super.remove(resource);
+ removeFromParents(resource, resource);
+ } finally {
+ endInput(null);
+ }
+
+ }
+
+ /**
+ * Reset the sync set so it is empty.
+ */
+ public void clear() {
+ try {
+ beginInput();
+ super.clear();
+ synchronized(this) {
+ parents.clear();
+ }
+ } finally {
+ endInput(null);
+ }
+ }
+
+ private synchronized boolean addToParents(IResource resource, IResource parent) {
+ if (parent.getType() == IResource.ROOT) {
+ return false;
+ }
+ // this flag is used to indicate if the parent was previosuly in the set
+ boolean addedParent = false;
+ if (parent.getType() == IResource.FILE) {
+ // the file is new
+ addedParent = true;
+ } else {
+ Set children = (Set)parents.get(parent.getFullPath());
+ if (children == null) {
+ children = new HashSet();
+ parents.put(parent.getFullPath(), children);
+ // this is a new folder in the sync set
+ addedParent = true;
+ }
+ children.add(resource);
+ }
+ // if the parent already existed and the resource is new, record it
+ if (!addToParents(resource, parent.getParent()) && addedParent) {
+ internalAddedSubtreeRoot(parent);
+ }
+ return addedParent;
+ }
+
+ private synchronized boolean removeFromParents(IResource resource, IResource parent) {
+ if (parent.getType() == IResource.ROOT) {
+ return false;
+ }
+ // this flag is used to indicate if the parent was removed from the set
+ boolean removedParent = false;
+ if (parent.getType() == IResource.FILE) {
+ // the file will be removed
+ removedParent = true;
+ } else {
+ Set children = (Set)parents.get(parent.getFullPath());
+ if (children != null) {
+ children.remove(resource);
+ if (children.isEmpty()) {
+ parents.remove(parent.getFullPath());
+ removedParent = true;
+ }
+ }
+ }
+ // if the parent wasn't removed and the resource was, record it
+ if (!removeFromParents(resource, parent.getParent()) && removedParent) {
+ internalRemovedSubtreeRoot(parent);
+ }
+ return removedParent;
+ }
+
+ private void internalAddedSubtreeRoot(IResource parent) {
+ ((SyncInfoTreeChangeEvent)getChangeEvent()).addedSubtreeRoot(parent);
+ }
+
+ private void internalRemovedSubtreeRoot(IResource parent) {
+ ((SyncInfoTreeChangeEvent)getChangeEvent()).removedSubtreeRoot(parent);
+ }
+
+ /**
+ * Remove from this set the <code>SyncInfo</code> for the given resource and any of its descendants
+ * within the specified depth. The depth is one of:
+ * <ul>
+ * <li><code>IResource.DEPTH_ZERO</code>: the resource only,
+ * <li><code>IResource.DEPTH_ONE</code>: the resource or its direct children,
+ * <li><code>IResource.DEPTH_INFINITE</code>: the resource and all of it's descendants.
+ * <ul>
+ * @param resource the root of the resource subtree
+ * @param depth the depth of the subtree
+ */
+ public void remove(IResource resource, int depth) {
+ try {
+ beginInput();
+ if (getSyncInfo(resource) != null) {
+ remove(resource);
+ }
+ if (depth == IResource.DEPTH_ZERO || resource.getType() == IResource.FILE) return;
+ if (depth == IResource.DEPTH_ONE) {
+ IResource[] members = members(resource);
+ for (int i = 0; i < members.length; i++) {
+ IResource member = members[i];
+ if (getSyncInfo(member) != null) {
+ remove(member);
+ }
+ }
+ } else if (depth == IResource.DEPTH_INFINITE) {
+ IResource [] toRemove = internalGetOutOfSyncDescendants((IContainer)resource);
+ for (int i = 0; i < toRemove.length; i++) {
+ remove(toRemove[i]);
+ }
+ }
+ } finally {
+ endInput(null);
+ }
+ }
+
+ protected synchronized IResource[] internalGetOutOfSyncDescendants(IContainer resource) {
+ // The parent map contains a set of all out-of-sync children
+ Set allChildren = (Set)parents.get(resource.getFullPath());
+ if (allChildren == null) return new IResource[0];
+ return (IResource[]) allChildren.toArray(new IResource[allChildren.size()]);
+ }
+
+ private synchronized IResource[] internalMembers(IWorkspaceRoot root) {
+ Set possibleChildren = parents.keySet();
+ Set children = new HashSet();
+ for (Iterator it = possibleChildren.iterator(); it.hasNext();) {
+ Object next = it.next();
+ IResource element = root.findMember((IPath)next);
+ if (element != null) {
+ children.add(element.getProject());
+ }
+ }
+ return (IResource[]) children.toArray(new IResource[children.size()]);
+ }
+
+ /**
+ * Return the immediate children of the given resource who are either out-of-sync
+ * or contain out-of-sync resources.
+ *
+ * @param resource the parent resource
+ * @return the children of the resource that are either out-of-sync or are ancestors of
+ * out-of-sync resources contained in the set
+ */
+ public synchronized IResource[] members(IResource resource) {
+ if (resource.getType() == IResource.FILE) return new IResource[0];
+ IContainer parent = (IContainer)resource;
+ if (parent.getType() == IResource.ROOT) return internalMembers((IWorkspaceRoot)parent);
+ // OPTIMIZE: could be optimized so that we don't traverse all the deep
+ // children to find the immediate ones.
+ Set children = new HashSet();
+ IPath path = parent.getFullPath();
+ Set possibleChildren = (Set)parents.get(path);
+ if(possibleChildren != null) {
+ for (Iterator it = possibleChildren.iterator(); it.hasNext();) {
+ Object next = it.next();
+ IResource element = (IResource)next;
+ IPath childPath = element.getFullPath();
+ IResource modelObject = null;
+ if(childPath.segmentCount() == (path.segmentCount() + 1)) {
+ modelObject = element;
+
+ } else if (childPath.segmentCount() > path.segmentCount()) {
+ IContainer childFolder = parent.getFolder(new Path(childPath.segment(path.segmentCount())));
+ modelObject = childFolder;
+ }
+ if (modelObject != null) {
+ children.add(modelObject);
+ }
+ }
+ }
+ return (IResource[]) children.toArray(new IResource[children.size()]);
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/package.html b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/package.html
new file mode 100644
index 000000000..c21ea5545
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/core/synchronize/package.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <meta content="text/html; charset=iso-8859-1"
+ http-equiv="Content-Type">
+ <meta content="IBM" name="Author">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Application programming interfaces for managing synchronization state.
+<h2>Package Specification</h2>
+<p>This package specifies the API for managing the synchronization
+state between the local workspace resources and a corresponding
+variants of those resources. The classes in this package can be used by
+Subscribers (see the org.eclipse.team.core.subscribers package) or
+others. The classes are roughly divided into three categories: <br>
+</p>
+<ul>
+ <li>describing the synchronization state of a single resource, <br>
+ </li>
+ <li>describing the synchronization state of a collection of
+resources, and <br>
+ </li>
+ <li>notifying interested parties of changes in the synchronization
+state.</li>
+</ul>
+<h3>Describing the synchronization state of a single resource</h3>
+<p>The following classes are provided in order to map a local resource
+to its corresponding variant resource and, for the case of a three-way
+compare, base resource.</p>
+<ul>
+ <li>SyncInfo: node which maps a local resource to a corresponding
+variant resource (and a base resource for three-way compare) and
+descibes the synchronization state of those resources (e.g. in-sync or
+incoming-change).</li>
+ <li>IResourceVariant: interface used by SyncInfo to access
+information about a variant resource, such as it's name, its type (file
+or container) and its contents.</li>
+ <li>IResourceVariantComparator: interface used by SyncInfo to
+indicate whether the comparison to be used is two-way or three-way and
+to compare either a local resource with a remote resource or, in the
+case of a three-way compare, two remote resources.</li>
+</ul>
+<h3>Describing the synchronization state of a collection of resources</h3>
+<p>The following classes are provided to accumulate multiple SyncInfo
+and to filter the accumulated set based on some selection criteria.</p>
+<ul>
+ <li>SyncInfoSet: a set which contains the out-of-sync SyncInfo for
+multiple local resources.</li>
+ <li>SyncInfoTree: a specialized set optimized for hierarchical
+resource based access (e.g. to obtain all out-of-sync children of a
+particular local resource).</li>
+</ul>
+<h3>Notifying interested parties of sync info set changes</h3>
+<p>Interested parties can register with a SyncInfoSet in order to
+receive notification when a set changes.</p>
+<ul>
+ <li>ISyncInfoSetChangeListener: implementors of this interface can be
+registered with a SyncInfoSet in order to recieve notification when the
+contents of the set change.</li>
+ <li>ISyncInfoSetChangeEvent: the type of the events generated by a
+SyncInfoSet</li>
+ <li>ISyncInfoTreeChangeEvent: specialized ISyncInfoSetChangeEvent
+generated by SyncInfoTree which includes notification of resource
+subtree aditions and removals.</li>
+</ul>
+<h3>Additional classes</h3>
+<p>There are also some additional classes provided to help manage
+SyncInfoSets</p>
+<ul>
+ <li>SyncInfoFilter: a filter that can be used to test SyncInfo. Long
+running tests are supported via an IProgressMonitor. SyncInfoSet has
+API for selecting and rejecting SyncInfo based on a provided filter.</li>
+ <li>FastSyncInfoFilter: a specialized filter that does not support
+the ue of a progress monitor</li>
+</ul>
+<p>Several common filters are provided as inner classes of the two
+filter classes for doing synchronization state tests and filter
+compounding (and, or, not).</p>
+<p>&nbsp;</p>
+</body>
+</html>
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/BackgroundEventHandler.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/BackgroundEventHandler.java
new file mode 100644
index 000000000..876e6fe64
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/BackgroundEventHandler.java
@@ -0,0 +1,362 @@
+/*******************************************************************************
+ * 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.team.internal.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
+import org.eclipse.team.core.TeamException;
+
+/**
+ * This class provides the infrastucture for processing/dispatching of events using a
+ * background job. This is useful in the following situations.
+ * <ul>
+ * <li>an operation is potentially long running but a resposive UI is desired
+ * while the operation is being performed.</li>
+ * <li>a change is a POST_CHANGE delta requires further modifications to the workspace
+ * which cannot be performed in the delta handler because the workspace is locked.</li>
+ * <li>a data structure is not thread safe and requires serialized operations.<li>
+ * </ul>
+ * </p>
+ * @since 3.0
+ */
+public abstract class BackgroundEventHandler {
+
+ // Events that need to be processed
+ private List awaitingProcessing = new ArrayList();
+
+ // The job that runs when events need to be processed
+ private Job eventHandlerJob;
+
+ // Indicate if the event handler has been shutdown
+ private boolean shutdown;
+
+ // Accumulate exceptions that occur
+ private ExceptionCollector errors;
+
+ // time the last dispath took
+ private long processingEventsDuration = 0L;
+
+ // time between event dispatches
+ private long DISPATCH_DELAY = 1500;
+
+ // time to wait for messages to be queued
+ private long WAIT_DELAY = 1000;
+
+ private String jobName;
+
+ /**
+ * Resource event class. The type is specific to subclasses.
+ */
+ public static class Event {
+ IResource resource;
+ int type;
+ int depth;
+ public Event(IResource resource, int type, int depth) {
+ this.resource = resource;
+ this.type = type;
+ this.depth = depth;
+ }
+ public int getDepth() {
+ return depth;
+ }
+ public IResource getResource() {
+ return resource;
+ }
+ public int getType() {
+ return type;
+ }
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("resource: "); //$NON-NLS-1$
+ buffer.append(resource.getFullPath());
+ buffer.append(" type: "); //$NON-NLS-1$
+ buffer.append(getTypeString());
+ buffer.append(" depth: "); //$NON-NLS-1$
+ buffer.append(getDepthString());
+ return buffer.toString();
+ }
+ protected String getDepthString() {
+ switch (depth) {
+ case IResource.DEPTH_ZERO :
+ return "DEPTH_ZERO"; //$NON-NLS-1$
+ case IResource.DEPTH_ONE :
+ return "DEPTH_ONE"; //$NON-NLS-1$
+ case IResource.DEPTH_INFINITE :
+ return "DEPTH_INFINITE"; //$NON-NLS-1$
+ default :
+ return "INVALID"; //$NON-NLS-1$
+ }
+ }
+ protected String getTypeString() {
+ return String.valueOf(type);
+ }
+ }
+
+ protected BackgroundEventHandler(String jobName, String errorTitle) {
+ this.jobName = jobName;
+ errors =
+ new ExceptionCollector(
+ errorTitle,
+ TeamPlugin.ID,
+ IStatus.ERROR,
+ null /* don't log */
+ );
+ createEventHandlingJob();
+ schedule();
+ }
+
+ /**
+ * Create the job used for processing the events in the queue. The job stops working when
+ * the queue is empty.
+ */
+ protected void createEventHandlingJob() {
+ eventHandlerJob = new Job(getName()) {
+ public IStatus run(IProgressMonitor monitor) {
+ return processEvents(monitor);
+ }
+ public boolean shouldRun() {
+ return ! isQueueEmpty();
+ }
+ public boolean shouldSchedule() {
+ return ! isQueueEmpty();
+ }
+ };
+ eventHandlerJob.addJobChangeListener(new JobChangeAdapter() {
+ public void done(IJobChangeEvent event) {
+ jobDone(event);
+ }
+ });
+ eventHandlerJob.setSystem(true);
+ eventHandlerJob.setPriority(Job.SHORT);
+ }
+
+ /**
+ * This method is invoked when the processing job completes. The
+ * default behavior of the handler is to restart the job if the queue
+ * is no longer empty and to clear the queue if the handler was shut down.
+ */
+ protected void jobDone(IJobChangeEvent event) {
+ if (isShutdown()) {
+ // The handler has been shutdown. Clean up the queue.
+ synchronized(this) {
+ awaitingProcessing.clear();
+ }
+ } else if (! isQueueEmpty()) {
+ // An event squeaked in as the job was finishing. Reschedule the job.
+ schedule();
+ }
+ }
+
+ /**
+ * Schedule the job to process the events now.
+ */
+ protected void schedule() {
+ eventHandlerJob.schedule();
+ }
+
+ /**
+ * Shutdown the event handler. Any events on the queue will be removed from the queue
+ * and will not be processed.
+ */
+ public void shutdown() {
+ shutdown = true;
+ eventHandlerJob.cancel();
+ }
+
+ /**
+ * Returns whether the handle has been shutdown.
+ * @return Returns whether the handle has been shutdown.
+ */
+ public boolean isShutdown() {
+ return shutdown;
+ }
+
+ /**
+ * Queue the event and start the job if it's not already doing work. If the job is
+ * already running then notify in case it was waiting.
+ * @param event the event to be queued
+ */
+ protected synchronized void queueEvent(Event event, boolean front) {
+ if (Policy.DEBUG_BACKGROUND_EVENTS) {
+ System.out.println("Event queued on " + getName() + ":" + event.toString()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (front) {
+ awaitingProcessing.add(0, event);
+ } else {
+ awaitingProcessing.add(event);
+ }
+ if (!isShutdown() && eventHandlerJob != null) {
+ if(eventHandlerJob.getState() == Job.NONE) {
+ schedule();
+ } else {
+ notify();
+ }
+ }
+ }
+
+ /**
+ * Return the name that is to be associated with the background job.
+ * @return the job name
+ */
+ protected String getName() {
+ return jobName;
+ }
+
+ /*
+ * Return the next event that has been queued, removing it from the queue.
+ * @return the next event in the queue
+ */
+ protected synchronized Event nextElement() {
+ if (isShutdown() || isQueueEmpty()) {
+ return null;
+ }
+ return (Event) awaitingProcessing.remove(0);
+ }
+
+ protected synchronized Event peek() {
+ if (isShutdown() || isQueueEmpty()) {
+ return null;
+ }
+ return (Event) awaitingProcessing.get(0);
+ }
+
+ /**
+ * Return whether there are unprocessed events on the event queue.
+ * @return whether there are unprocessed events on the queue
+ */
+ protected synchronized boolean isQueueEmpty() {
+ return awaitingProcessing.isEmpty();
+ }
+
+ /**
+ * Process events from the events queue and dispatch results. This method does not
+ * directly check for or handle cancelation of the provided monitor. However,
+ * it does invoke <code>processEvent(Event)</code> which may check for and handle
+ * cancelation by shuting down the receiver.
+ * <p>
+ * The <code>isReadyForDispatch()</code> method is used in conjuntion
+ * with the <code>dispatchEvents(IProgressMonitor)</code> to allow
+ * the output of the event handler to be batched in order to avoid
+ * fine grained UI updating.
+ * @param monitor a progress monitor
+ */
+ protected IStatus processEvents(IProgressMonitor monitor) {
+ errors.clear();
+ try {
+ // It's hard to know how much work is going to happen
+ // since the queue can grow. Use the current queue size as a hint to
+ // an infinite progress monitor
+ monitor.beginTask(null, 100);
+ IProgressMonitor subMonitor = Policy.infiniteSubMonitorFor(monitor, 90);
+ subMonitor.beginTask(null, 1024);
+
+ Event event;
+ processingEventsDuration = System.currentTimeMillis();
+ while ((event = nextElement()) != null && ! isShutdown()) {
+ try {
+ processEvent(event, subMonitor);
+ if (Policy.DEBUG_BACKGROUND_EVENTS) {
+ System.out.println("Event processed on " + getName() + ":" + event.toString()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if(isReadyForDispatch(true /*wait if queue is empty*/)) {
+ dispatchEvents(Policy.subMonitorFor(subMonitor, 1));
+ eventsDispatched();
+ }
+ } catch (CoreException e) {
+ // handle exception but keep going
+ handleException(e);
+ }
+ }
+ } finally {
+ monitor.done();
+ }
+ return errors.getStatus();
+ }
+
+ protected void eventsDispatched() {
+ processingEventsDuration = System.currentTimeMillis();
+ }
+
+ /**
+ * Notify clients of processed events.
+ * @param monitor a progress monitor
+ */
+ protected abstract void dispatchEvents(IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * Returns <code>true</code> if processed events should be dispatched and
+ * <code>false</code> otherwise. Events are dispatched at regular intervals
+ * to avoid fine grain events causing the UI to be too jumpy. Also, if the
+ * events queue is empty we will wait a small amount of time to allow
+ * pending events to be queued. The queueEvent notifies when events are
+ * queued.
+ * @return <code>true</code> if processed events should be dispatched and
+ * <code>false</code> otherwise
+ */
+ protected boolean isReadyForDispatch(boolean wait) {
+ long duration = System.currentTimeMillis() - processingEventsDuration;
+ if(duration >= DISPATCH_DELAY) {
+ return true;
+ }
+ synchronized(this) {
+ if(! isQueueEmpty() || ! wait) {
+ return false;
+ }
+ try {
+ wait(WAIT_DELAY);
+ } catch (InterruptedException e) {
+ // just continue
+ }
+ }
+ return isQueueEmpty();
+ }
+
+ /**
+ * Handle the exception by recording it in the errors list.
+ * @param e
+ */
+ protected void handleException(CoreException e) {
+ errors.handleException(e);
+
+ }
+
+ /**
+ * Process the event in the context of a running background job. Subclasses may
+ * (but are not required to) check the provided monitor for cancelation and shut down the
+ * receiver by invoking the <code>shutdown()</code> method.
+ * <p>
+ * In many cases, a background event handler will translate incoming events into outgoing
+ * events. If this is the case, the handler should accumulate these events in the
+ * <code>proceessEvent</code> method and propogate them from the <code>dispatchEvent</code>
+ * method which is invoked periodically in order to batch outgoing events and avoid
+ * the UI becoming too jumpy.
+ *
+ * @param event the <code>Event</code> to be processed
+ * @param monitor a progress monitor
+ */
+ protected abstract void processEvent(Event event, IProgressMonitor monitor) throws CoreException;
+
+ /**
+ * Return the job from which the <code>processedEvent</code> method is invoked.
+ * @return Returns the background event handlig job.
+ */
+ public Job getEventHandlerJob() {
+ return eventHandlerJob;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DefaultFileModificationValidator.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DefaultFileModificationValidator.java
index 82bf0d998..90e5c154a 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DefaultFileModificationValidator.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DefaultFileModificationValidator.java
@@ -10,20 +10,17 @@
*******************************************************************************/
package org.eclipse.team.internal.core;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IFileModificationValidator;
-import org.eclipse.core.resources.IResourceStatus;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.MultiStatus;
-import org.eclipse.core.runtime.Status;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
import org.eclipse.team.core.Team;
public class DefaultFileModificationValidator implements IFileModificationValidator {
private static final Status OK = Team.OK_STATUS;
private IStatus getDefaultStatus(IFile file) {
- return file.isReadOnly()
- ? new Status(Status.ERROR, TeamPlugin.ID, IResourceStatus.READ_ONLY_LOCAL, Policy.bind("FileModificationValidator.fileIsReadOnly", file.getFullPath().toString()), null) //$NON-NLS-1$
+ return
+ file.isReadOnly()
+ ? new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.READ_ONLY_LOCAL, Policy.bind("FileModificationValidator.fileIsReadOnly", file.getFullPath().toString()), null) //$NON-NLS-1$
: OK;
}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DeploymentProviderManager.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DeploymentProviderManager.java
new file mode 100644
index 000000000..018c8a860
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DeploymentProviderManager.java
@@ -0,0 +1,447 @@
+/*******************************************************************************
+ * 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.team.internal.core;
+
+import java.io.*;
+import java.util.*;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.team.core.*;
+import org.eclipse.team.internal.core.registry.DeploymentProviderDescriptor;
+import org.eclipse.team.internal.core.registry.DeploymentProviderRegistry;
+import org.eclipse.team.internal.core.Policy;
+
+public class DeploymentProviderManager implements IDeploymentProviderManager, IResourceChangeListener {
+
+ // key for remembering if state has been loaded for a project
+ private final static QualifiedName STATE_LOADED_KEY = new QualifiedName("org.eclipse.team.core.deployment", "state_restored_key"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ // {project -> list of Mapping}
+ private Map mappings = new HashMap(5);
+
+ // registry for deployment provider extensions
+ private DeploymentProviderRegistry registry;
+
+ // lock to ensure that map/unmap and getProvider support concurrency
+ private static final ILock mappingLock = Platform.getJobManager().newLock();
+
+ // persistence constants
+ private final static String CTX_PROVIDERS = "deploymentProviders"; //$NON-NLS-1$
+ private final static String CTX_PROVIDER = "provider"; //$NON-NLS-1$
+ private final static String CTX_ID = "id"; //$NON-NLS-1$
+ private final static String CTX_PATH = "container_path"; //$NON-NLS-1$
+ private final static String CTX_PROVIDER_DATA = "data"; //$NON-NLS-1$
+ private final static String FILENAME = ".deployments"; //$NON-NLS-1$
+
+ static class Mapping {
+ private DeploymentProviderDescriptor descriptor;
+ private DeploymentProvider provider;
+ private IContainer container;
+ private IMemento savedState;
+
+ Mapping(DeploymentProviderDescriptor descriptor, IContainer container) {
+ this.descriptor = descriptor;
+ this.container = container;
+ }
+ public DeploymentProvider getProvider() throws TeamException {
+ if(provider == null) {
+ try {
+ this.provider = descriptor.createProvider();
+ this.provider.setContainer(container);
+ this.provider.restoreState(savedState);
+ this.savedState = null;
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+ return provider;
+ }
+ public void setProvider(DeploymentProvider provider) {
+ this.provider = provider;
+ this.savedState = null;
+ }
+ public IContainer getContainer() {
+ return container;
+ }
+ public DeploymentProviderDescriptor getDescription() {
+ return descriptor;
+ }
+ public void setProviderState(IMemento savedState) {
+ this.savedState = savedState;
+ }
+ }
+
+ public DeploymentProviderManager() {
+ registry = new DeploymentProviderRegistry();
+ }
+
+ public void map(IContainer container, DeploymentProvider deploymentProvider) throws TeamException {
+ try {
+ mappingLock.acquire();
+ if (!deploymentProvider.isMultipleMappingsSupported()) {
+ // don't allow is overlapping deployment providers of the same type
+ checkOverlapping(container, deploymentProvider.getID());
+ }
+
+ // extension point descriptor must exist
+ DeploymentProviderDescriptor descriptor = registry.find(deploymentProvider.getID());
+ if(descriptor == null) {
+ throw new TeamException(Policy.bind("DeploymentProviderManager.10", deploymentProvider.getID())); //$NON-NLS-1$
+ }
+
+ // create the new mapping
+ Mapping m = internalMap(container, descriptor);
+ m.setProvider(deploymentProvider);
+ deploymentProvider.setContainer(container);
+ deploymentProvider.init();
+
+ saveState(container.getProject());
+ // TODO: what kind of event is generated when one is mapped?
+ } finally {
+ mappingLock.release();
+ }
+ }
+
+ public void unmap(IContainer container, DeploymentProvider teamProvider) throws TeamException {
+ try {
+ mappingLock.acquire();
+ IProject project = container.getProject();
+ List projectMaps = internalGetMappings(container);
+ Mapping[] m = internalGetMappingsFor(container, teamProvider.getID());
+ for (int i = 0; i < m.length; i++) {
+ Mapping mapping = m[i];
+ if (mapping.getProvider() == teamProvider) {
+ projectMaps.remove(mapping);
+ if(projectMaps.isEmpty()) {
+ mappings.remove(project);
+ }
+ }
+ }
+
+ // dispose of provider
+ teamProvider.dispose();
+ saveState(container.getProject());
+
+ // TODO: what kind of event is sent when unmapped?
+ } finally {
+ mappingLock.release();
+ }
+ }
+
+ public DeploymentProvider[] getMappings(IResource resource) {
+ List projectMappings = internalGetMappings(resource);
+ String fullPath = resource.getFullPath().toString();
+ List result = new ArrayList();
+ if(projectMappings != null) {
+ for (Iterator it = projectMappings.iterator(); it.hasNext();) {
+ Mapping m = (Mapping) it.next();
+ if(fullPath.startsWith(m.getContainer().getFullPath().toString())) {
+ try {
+ // lazy initialize of provider must be supported
+ // TODO: It is possible that the provider has been unmap concurrently
+ result.add(m.getProvider());
+ } catch (CoreException e) {
+ TeamPlugin.log(e);
+ }
+ }
+ }
+ }
+ return (DeploymentProvider[]) result.toArray(new DeploymentProvider[result.size()]);
+ }
+
+ public DeploymentProvider[] getMappings(IResource resource, String id) {
+ Mapping[] m = internalGetMappingsFor(resource, id);
+ List result = new ArrayList();
+ for (int i = 0; i < m.length; i++) {
+ Mapping mapping = m[i];
+ try {
+ // lazy initialize of provider must be supported
+ // TODO: It is possible that the provider has been unmap concurrently
+ result.add(mapping.getProvider());
+ } catch (TeamException e) {
+ TeamPlugin.log(e);
+ }
+ }
+
+ DeploymentProvider[] providers = (DeploymentProvider[]) result.toArray(new DeploymentProvider[result.size()]);
+ // Ensure that multiple providers are not mapped if it is not supported
+ // by the provider type. This could occur if the deployment configuration
+ // was loaded from a repository or modified manually
+ if (providers.length > 1 && !providers[0].isMultipleMappingsSupported()) {
+ // Log and ignore all but one of the mappings
+ TeamPlugin.log(IStatus.WARNING, Policy.bind("DeploymentProviderManager.12", resource.getFullPath().toString(), id), null); //$NON-NLS-1$
+ return new DeploymentProvider[] { providers[0] };
+ }
+ return providers;
+ }
+
+ public boolean getMappedTo(IResource resource, String id) {
+ return internalGetMappingsFor(resource, id).length > 0;
+ }
+
+ private void checkOverlapping(IContainer container, String id) throws TeamException {
+ List projectMappings = internalGetMappings(container);
+ String fullPath = container.getFullPath().toString();
+ if(projectMappings != null) {
+ for (Iterator it = projectMappings.iterator(); it.hasNext();) {
+ Mapping m = (Mapping) it.next();
+ String first = m.getContainer().getFullPath().toString();
+ if(fullPath.startsWith(first) || first.startsWith(fullPath)) {
+ if (m.getDescription().getId().equals(id)) {
+ throw new TeamException(Policy.bind("DeploymentProviderManager.13", container.getFullPath().toString(), m.getDescription().getId())); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+ }
+
+ private Mapping internalMap(IContainer container, DeploymentProviderDescriptor description) {
+ Mapping newMapping = new Mapping(description, container);
+ return internalMap(container, newMapping);
+ }
+
+ private Mapping internalMap(IContainer container, Mapping newMapping) {
+ IProject project = container.getProject();
+ List projectMaps = (List)mappings.get(project);
+ if(projectMaps == null) {
+ projectMaps = new ArrayList();
+ mappings.put(project, projectMaps);
+ }
+ projectMaps.add(newMapping);
+ return newMapping;
+ }
+
+ /*
+ * Loads all the mappings associated with the resource's project.
+ */
+ private List internalGetMappings(IResource resource) {
+ try {
+ mappingLock.acquire();
+ IProject project = resource.getProject();
+ List m = (List)mappings.get(project);
+ try {
+ if(project.getSessionProperty(STATE_LOADED_KEY) != null) {
+ return m;
+ }
+ Mapping[] projectMappings = loadMappings(project);
+ for (int i = 0; i < projectMappings.length; i++) {
+ Mapping mapping = projectMappings[i];
+ internalMap(mapping.getContainer(), mapping);
+ }
+
+ project.setSessionProperty(STATE_LOADED_KEY, new Object());
+ } catch (TeamException e) {
+ } catch (CoreException e) {
+ }
+ return (List)mappings.get(project);
+ } finally {
+ mappingLock.release();
+ }
+ }
+
+ private Mapping[] internalGetMappingsFor(IResource resource, String id) {
+ List projectMappings = internalGetMappings(resource);
+ List result = new ArrayList();
+ String fullPath = resource.getFullPath().toString();
+ if(projectMappings != null) {
+ for (Iterator it = projectMappings.iterator(); it.hasNext();) {
+ Mapping m = (Mapping) it.next();
+ // mapping can be initialize without having provider loaded yet!
+ if(m.getDescription().getId().equals(id) && fullPath.startsWith(m.getContainer().getFullPath().toString())) {
+ result.add(m);
+ }
+ }
+ }
+ return (Mapping[]) result.toArray(new Mapping[result.size()]);
+ }
+
+ /**
+ * Saves a file containing the list of participant ids that are registered with this
+ * manager. Each participant is also given the chance to save it's state.
+ */
+ private void saveState(IProject project) throws TeamException {
+ File file = getNonsharedSettingsFile(project);
+ try {
+ XMLMemento xmlMemento = XMLMemento.createWriteRoot(CTX_PROVIDERS);
+ List providers = (List)mappings.get(project);
+ if(providers == null) {
+ if (file.exists()) {
+ file.delete();
+ }
+ } else {
+ for (Iterator it2 = providers.iterator(); it2.hasNext(); ) {
+ Mapping mapping = (Mapping) it2.next();
+ IMemento node = xmlMemento.createChild(CTX_PROVIDER);
+ node.putString(CTX_ID, mapping.getDescription().getId());
+ node.putString(CTX_PATH, mapping.getContainer().getProjectRelativePath().toString());
+ mapping.getProvider().saveState(node.createChild(CTX_PROVIDER_DATA));
+ }
+ Writer writer = new BufferedWriter(new FileWriter(file));
+ try {
+ xmlMemento.save(writer);
+ } finally {
+ writer.close();
+ }
+ }
+ } catch (IOException e) {
+ throw new TeamException(Policy.bind("DeploymentProviderManager.15", project.getName()), e); //$NON-NLS-1$
+ } catch(CoreException ce) {
+ throw TeamException.asTeamException(ce);
+ }
+ }
+
+ /**
+ * @param project
+ * @return
+ */
+ private File getNonsharedSettingsFile(IProject project) {
+ IPath metaPath = project.getPluginWorkingLocation(TeamPlugin.getPlugin().getDescriptor());
+ metaPath = metaPath.append(FILENAME);
+ File file = metaPath.toFile();
+ return file;
+ }
+
+ /*
+ * Load the mappings for the given project and return them.
+ */
+ private Mapping[] loadMappings(IProject project) throws TeamException, CoreException {
+ File file = getNonsharedSettingsFile(project);
+ if(! file.exists()) {
+ // The file may have been deleted before our delta listener was loaded.
+ // If there are any deployments stored in the meta data area, dispose of them
+ // TODO: See if there were any before and dispose of them
+ return new Mapping[0];
+ }
+ Reader reader;
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ } catch (FileNotFoundException e) {
+ return new Mapping[0];
+ }
+ return loadMappings(project, reader);
+ }
+
+ private Mapping[] loadMappings(IProject project, Reader reader) throws TeamException {
+ try {
+ IMemento memento = XMLMemento.createReadRoot(reader);
+ IMemento[] providers = memento.getChildren(CTX_PROVIDER);
+ List projectMappings = new ArrayList();
+ for (int i = 0; i < providers.length; i++) {
+ IMemento memento2 = providers[i];
+ String id = memento2.getString(CTX_ID);
+ IPath location = new Path(memento2.getString(CTX_PATH));
+
+ if(! project.exists(location)) {
+ TeamPlugin.log(IStatus.ERROR, Policy.bind("DeploymentProviderManager.16", location.toString(), project.getName()), null); //$NON-NLS-1$
+ }
+ IResource resource = location.isEmpty() ? (IContainer)project : project.findMember(location);
+ if (resource.getType() == IResource.FILE) {
+ TeamPlugin.log(IStatus.ERROR, Policy.bind("DeploymentProviderManager.17", location.toString(), project.getName()), null); //$NON-NLS-1$
+ }
+ IContainer container = (IContainer)resource;
+ DeploymentProviderDescriptor desc = registry.find(id);
+ if(desc != null) {
+ Mapping m = new Mapping(desc, container);
+ m.setProviderState(memento2.getChild(CTX_PROVIDER_DATA));
+ projectMappings.add(m);
+ } else {
+ TeamPlugin.log(IStatus.ERROR, Policy.bind("SynchronizeManager.9", id), null); //$NON-NLS-1$
+ }
+ }
+ return (Mapping[]) projectMappings.toArray(new Mapping[projectMappings.size()]);
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.IDeploymentProviderManager#getDeploymentProviderRoots(java.lang.String)
+ */
+ public IResource[] getDeploymentProviderRoots(String id) {
+ IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+ Set roots = new HashSet();
+ for (int i = 0; i < projects.length; i++) {
+ IProject project = projects[i];
+ List mappings = internalGetMappings(project);
+ if (mappings != null) {
+ for (Iterator iter = mappings.iterator(); iter.hasNext();) {
+ Mapping mapping = (Mapping) iter.next();
+ if (id == null || mapping.getDescription().getId().equals(id)) {
+ roots.add(mapping.getContainer());
+ }
+ }
+ }
+ }
+ return (IResource[]) roots.toArray(new IResource[roots.size()]);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
+ */
+ public void resourceChanged(IResourceChangeEvent event) {
+ processDelta(event.getDelta());
+ }
+
+ private void processDelta(IResourceDelta delta) {
+ IResource resource = delta.getResource();
+ int kind = delta.getKind();
+
+ if (resource.getType() == IResource.PROJECT) {
+ // Handle a deleted project
+ if (((kind & IResourceDelta.REMOVED) != 0)) {
+ handleProjectRemoval((IProject)resource);
+ return;
+ }
+ // Handle a closed project
+ if ((delta.getFlags() & IResourceDelta.OPEN) != 0 && !((IProject) resource).isOpen()) {
+ handleProjectClose((IProject)resource);
+ return;
+ }
+ }
+
+ if (((kind & IResourceDelta.REMOVED) != 0) && (resource.getType() == IResource.FOLDER)) {
+ handleFolderRemoval((IFolder)resource);
+ }
+
+ // Handle changed children
+ IResourceDelta[] affectedChildren = delta.getAffectedChildren(IResourceDelta.CHANGED | IResourceDelta.REMOVED | IResourceDelta.ADDED);
+ for (int i = 0; i < affectedChildren.length; i++) {
+ processDelta(affectedChildren[i]);
+ }
+ }
+
+ private void handleFolderRemoval(IFolder folder) {
+ DeploymentProvider[] providers = getMappings(folder);
+ for (int i = 0; i < providers.length; i++) {
+ DeploymentProvider provider = providers[i];
+ try {
+ unmap(folder, provider);
+ } catch (TeamException e) {
+ TeamPlugin.log(e);
+ }
+ }
+ }
+
+ private void handleProjectClose(IProject project) {
+ mappings.remove(project);
+ }
+
+ private void handleProjectRemoval(IProject project) {
+ mappings.remove(project);
+ }
+
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IJobListener.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IJobListener.java
new file mode 100644
index 000000000..3962d7f1d
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IJobListener.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * 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.team.internal.core;
+
+import org.eclipse.core.runtime.QualifiedName;
+
+/**
+ * This interface allows interested parties to receive notification
+ * when work has started or stopped for a given job type. The <code>started</code>
+ * method is invoked when the first job is started for the given <code>jobType</code>.
+ * The <code>finish</code> method is called when the last job of a given type stops.
+ * Several jobs for the job type may start and stop in the interum without causing
+ * notification to the listener.
+ */
+public interface IJobListener {
+ public void started(QualifiedName jobType);
+ public void finished(QualifiedName jobType);
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IMemento.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IMemento.java
new file mode 100644
index 000000000..fff33a1b7
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IMemento.java
@@ -0,0 +1,169 @@
+/*******************************************************************************
+ * 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.team.internal.core;
+
+/**
+ * Interface to a memento used for saving the important state of an object
+ * in a form that can be persisted in the file system.
+ * <p>
+ * Mementos were designed with the following requirements in mind:
+ * <ol>
+ * <li>Certain objects need to be saved and restored across platform sessions.
+ * </li>
+ * <li>When an object is restored, an appropriate class for an object might not
+ * be available. It must be possible to skip an object in this case.</li>
+ * <li>When an object is restored, the appropriate class for the object may be
+ * different from the one when the object was originally saved. If so, the
+ * new class should still be able to read the old form of the data.</li>
+ * </ol>
+ * </p>
+ * <p>
+ * Mementos meet these requirements by providing support for storing a
+ * mapping of arbitrary string keys to primitive values, and by allowing
+ * mementos to have other mementos as children (arranged into a tree).
+ * A robust external storage format based on XML is used.
+ * </p><p>
+ * The key for an attribute may be any alpha numeric value. However, the
+ * value of <code>TAG_ID</code> is reserved for internal use.
+ * </p><p>
+ * This interface is not intended to be implemented or extended by clients.
+ * </p>
+ *
+ * @see IPersistableElement
+ * @see IElementFactory
+ */
+public interface IMemento {
+ /**
+ * Special reserved key used to store the memento id
+ * (value <code>"org.eclipse.ui.id"</code>).
+ *
+ * @see #getID()
+ */
+ public static final String TAG_ID = "IMemento.internal.id"; //$NON-NLS-1$
+ /**
+ * Creates a new child of this memento with the given type.
+ * <p>
+ * The <code>getChild</code> and <code>getChildren</code> methods
+ * are used to retrieve children of a given type.
+ * </p>
+ *
+ * @param type the type
+ * @return a new child memento
+ * @see #getChild
+ * @see #getChildren
+ */
+ public IMemento createChild(String type);
+ /**
+ * Creates a new child of this memento with the given type and id.
+ * The id is stored in the child memento (using a special reserved
+ * key, <code>TAG_ID</code>) and can be retrieved using <code>getId</code>.
+ * <p>
+ * The <code>getChild</code> and <code>getChildren</code> methods
+ * are used to retrieve children of a given type.
+ * </p>
+ *
+ * @param type the type
+ * @param id the child id
+ * @return a new child memento with the given type and id
+ * @see #getID
+ */
+ public IMemento createChild(String type, String id);
+ /**
+ * Returns the first child with the given type id.
+ *
+ * @param type the type id
+ * @return the first child with the given type
+ */
+ public IMemento getChild(String type);
+ /**
+ * Returns all children with the given type id.
+ *
+ * @param type the type id
+ * @return the list of children with the given type
+ */
+ public IMemento[] getChildren(String type);
+ /**
+ * Returns the floating point value of the given key.
+ *
+ * @param key the key
+ * @return the value, or <code>null</code> if the key was not found or was found
+ * but was not a floating point number
+ */
+ public Float getFloat(String key);
+ /**
+ * Returns the id for this memento.
+ *
+ * @return the memento id, or <code>null</code> if none
+ * @see #createChild(java.lang.String,java.lang.String)
+ */
+ public String getID();
+ /**
+ * Returns the integer value of the given key.
+ *
+ * @param key the key
+ * @return the value, or <code>null</code> if the key was not found or was found
+ * but was not an integer
+ */
+ public Integer getInteger(String key);
+ /**
+ * Returns the string value of the given key.
+ *
+ * @param key the key
+ * @return the value, or <code>null</code> if the key was not found
+ */
+ public String getString(String key);
+ /**
+ * Returns the data of the Text node of the memento. Each memento is allowed
+ * only one Text node.
+ *
+ * @return the data of the Text node of the memento, or <code>null</code>
+ * if the memento has no Text node.
+ * @since 2.0
+ */
+ public String getTextData();
+ /**
+ * Sets the value of the given key to the given floating point number.
+ *
+ * @param key the key
+ * @param value the value
+ */
+ public void putFloat(String key, float value);
+ /**
+ * Sets the value of the given key to the given integer.
+ *
+ * @param key the key
+ * @param value the value
+ */
+ public void putInteger(String key, int value);
+ /**
+ * Copy the attributes and children from <code>memento</code>
+ * to the receiver.
+ *
+ * @param memento the IMemento to be copied.
+ */
+ public void putMemento(IMemento memento);
+ /**
+ * Sets the value of the given key to the given string.
+ *
+ * @param key the key
+ * @param value the value
+ */
+ public void putString(String key, String value);
+ /**
+ * Sets the memento's Text node to contain the given data. Creates the Text node if
+ * none exists. If a Text node does exist, it's current contents are replaced.
+ * Each memento is allowed only one text node.
+ *
+ * @param data the data to be placed on the Text node
+ * @since 2.0
+ */
+ public void putTextData(String data);
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java
new file mode 100644
index 000000000..cda7872e2
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java
@@ -0,0 +1,250 @@
+/*******************************************************************************
+ * 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.team.internal.core;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.CachedResourceVariant;
+
+/**
+ * This class implements a caching facility that can be used by TeamProviders to cache contents
+ */
+public class ResourceVariantCache {
+
+ // Directory to cache file contents
+ private static final String CACHE_DIRECTORY = ".cache"; //$NON-NLS-1$
+ // Maximum lifespan of local cache file, in milliseconds
+ private static final long CACHE_FILE_LIFESPAN = 60*60*1000; // 1hr
+
+ // Map of registered cahces indexed by local name of a QualifiedName
+ private static Map caches = new HashMap(); // String (local name) > RemoteContentsCache
+
+ private String name;
+ private Map cacheEntries;
+ private long lastCacheCleanup;
+ private int cacheDirSize;
+
+ // Lock used to serialize the writting of cache contents
+ private ILock lock = Platform.getJobManager().newLock();
+
+ /**
+ * Enables the use of remote contents caching for the given cacheId. The cache ID must be unique.
+ * A good candidate for this ID is the plugin ID of the plugin peforming the caching.
+ *
+ * @param cacheId the unique Id of the cache being enabled
+ * @throws TeamException if the cache area on disk could not be properly initialized
+ */
+ public static synchronized void enableCaching(String cacheId) {
+ if (isCachingEnabled(cacheId)) return;
+ ResourceVariantCache cache = new ResourceVariantCache(cacheId);
+ try {
+ cache.createCacheDirectory();
+ } catch (TeamException e) {
+ // Log the exception and continue
+ TeamPlugin.log(e);
+ }
+ caches.put(cacheId, cache);
+ }
+
+ /**
+ * Returns whether caching has been enabled for the given Id. A cache should only be enabled once.
+ * It is conceivable that a cache be persisted over workbench invocations thus leading to a cahce that
+ * is enabled on startup without intervention by the owning plugin.
+ *
+ * @param cacheId the unique Id of the cache
+ * @return true if caching for the given Id is enabled
+ */
+ public static boolean isCachingEnabled(String cacheId) {
+ return getCache(cacheId) != null;
+ }
+
+ /**
+ * Disable the cache, dispoing of any file contents in the cache.
+ *
+ * @param cacheId the unique Id of the cache
+ * @throws TeamException if the cached contents could not be deleted from disk
+ */
+ public static void disableCache(String cacheId) {
+ ResourceVariantCache cache = getCache(cacheId);
+ if (cache == null) {
+ // There is no cache to dispose of
+ return;
+ }
+ caches.remove(cacheId);
+ try {
+ cache.deleteCacheDirectory();
+ } catch (TeamException e) {
+ // Log the exception and continue
+ TeamPlugin.log(e);
+ }
+ }
+
+ /**
+ * Return the cache for the given id or null if caching is not enabled for the given id.
+ * @param cacheId
+ * @return
+ */
+ public static synchronized ResourceVariantCache getCache(String cacheId) {
+ return (ResourceVariantCache)caches.get(cacheId);
+ }
+
+ public static synchronized void shutdown() {
+ for (Iterator iter = caches.keySet().iterator(); iter.hasNext();) {
+ String id = (String) iter.next();
+ disableCache(id);
+ }
+ }
+
+ private ResourceVariantCache(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Return whether the cache contains an entry for the given id. Register a hit if it does.
+ * @param id the id of the cache entry
+ * @return true if there are contents cached for the id
+ */
+ public boolean hasEntry(String id) {
+ return internalGetCacheEntry(id) != null;
+ }
+
+ protected IPath getCachePath() {
+ return getStateLocation().append(CACHE_DIRECTORY).append(name);
+ }
+
+ private IPath getStateLocation() {
+ return TeamPlugin.getPlugin().getStateLocation();
+ }
+
+ private synchronized void clearOldCacheEntries() {
+ long current = new Date().getTime();
+ if ((lastCacheCleanup!=-1) && (current - lastCacheCleanup < CACHE_FILE_LIFESPAN)) return;
+ List stale = new ArrayList();
+ for (Iterator iter = cacheEntries.values().iterator(); iter.hasNext();) {
+ ResourceVariantCacheEntry entry = (ResourceVariantCacheEntry) iter.next();
+ long lastHit = entry.getLastAccessTimeStamp();
+ if ((current - lastHit) > CACHE_FILE_LIFESPAN){
+ stale.add(entry);
+ }
+ }
+ for (Iterator iter = stale.iterator(); iter.hasNext();) {
+ ResourceVariantCacheEntry entry = (ResourceVariantCacheEntry) iter.next();
+ entry.dispose();
+ }
+ }
+
+ private synchronized void purgeFromCache(String id) {
+ ResourceVariantCacheEntry entry = (ResourceVariantCacheEntry)cacheEntries.get(id);
+ File f = entry.getFile();
+ try {
+ deleteFile(f);
+ } catch (TeamException e) {
+ // Ignore the deletion failure.
+ // A failure only really matters when purging the directory on startup
+ }
+ cacheEntries.remove(id);
+ }
+
+ private synchronized void createCacheDirectory() throws TeamException {
+ IPath cacheLocation = getCachePath();
+ File file = cacheLocation.toFile();
+ if (file.exists()) {
+ deleteFile(file);
+ }
+ if (! file.mkdirs()) {
+ throw new TeamException(Policy.bind("RemoteContentsCache.fileError", file.getAbsolutePath())); //$NON-NLS-1$
+ }
+ cacheEntries = new HashMap();
+ lastCacheCleanup = -1;
+ cacheDirSize = 0;
+ }
+
+ private synchronized void deleteCacheDirectory() throws TeamException {
+ cacheEntries = null;
+ lastCacheCleanup = -1;
+ cacheDirSize = 0;
+ IPath cacheLocation = getCachePath();
+ File file = cacheLocation.toFile();
+ if (file.exists()) {
+ try {
+ deleteFile(file);
+ } catch (TeamException e) {
+ // Don't worry about problems deleting.
+ // The only case that matters is when the cache directory is created
+ }
+ }
+ }
+
+ private void deleteFile(File file) throws TeamException {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ for (int i = 0; i < children.length; i++) {
+ deleteFile(children[i]);
+ }
+ }
+ if (! file.delete()) {
+ throw new TeamException(Policy.bind("RemoteContentsCache.fileError", file.getAbsolutePath())); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Purge the given cache entry from the cache. This method should only be invoked from
+ * an instance of ResourceVariantCacheEntry after it has set it's state to DISPOSED.
+ * @param entry
+ */
+ protected void purgeFromCache(ResourceVariantCacheEntry entry) {
+ purgeFromCache(entry.getId());
+ }
+
+ private synchronized ResourceVariantCacheEntry internalGetCacheEntry(String id) {
+ if (cacheEntries == null) {
+ // This probably means that the cache has been disposed
+ throw new IllegalStateException(Policy.bind("RemoteContentsCache.cacheDisposed", name)); //$NON-NLS-1$
+ }
+ ResourceVariantCacheEntry entry = (ResourceVariantCacheEntry)cacheEntries.get(id);
+ if (entry != null) {
+ entry.registerHit();
+ }
+ return entry;
+ }
+
+ /**
+ * @param id the id that uniquely identifes the remote resource that is cached.
+ * @return
+ */
+ public ResourceVariantCacheEntry getCacheEntry(String id) {
+ return internalGetCacheEntry(id);
+ }
+
+ public synchronized ResourceVariantCacheEntry add(String id, CachedResourceVariant resource) {
+ clearOldCacheEntries();
+ String filePath = String.valueOf(cacheDirSize++);
+ ResourceVariantCacheEntry entry = new ResourceVariantCacheEntry(this, lock, id, filePath);
+ entry.setResourceVariant(resource);
+ cacheEntries.put(id, entry);
+ return entry;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCacheEntry.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCacheEntry.java
new file mode 100644
index 000000000..860028151
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCacheEntry.java
@@ -0,0 +1,217 @@
+/*******************************************************************************
+ * 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.team.internal.core;
+
+import java.io.*;
+import java.util.Date;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.CachedResourceVariant;
+
+/**
+ * This class provides the implementation for the ICacheEntry
+ */
+public class ResourceVariantCacheEntry {
+
+ public static final int UNINITIALIZED = 0;
+ public static final int READY = 1;
+ public static final int DISPOSED = 2;
+
+ private String id;
+ private String filePath;
+ private ResourceVariantCache cache;
+ private byte[] syncBytes;
+ private int state = UNINITIALIZED;
+ private long lastAccess;
+ private CachedResourceVariant resourceVariant;
+ private ILock lock;
+
+ public ResourceVariantCacheEntry(ResourceVariantCache cache, ILock lock, String id, String filePath) {
+ this.lock = lock;
+ state = UNINITIALIZED;
+ this.cache = cache;
+ this.id = id;
+ this.filePath = filePath;
+ registerHit();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ICacheEntry#getContents()
+ */
+ public InputStream getContents() throws TeamException {
+ if (state != READY) return null;
+ registerHit();
+ File ioFile = getFile();
+ try {
+ try {
+ if (ioFile.exists()) {
+ return new FileInputStream(ioFile);
+ }
+ } catch (IOException e) {
+ // Try to purge the cache and continue
+ cache.purgeFromCache(this);
+ throw e;
+ }
+ } catch (IOException e) {
+ // We will end up here if we couldn't read or delete the cache file
+ throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$
+ }
+ // This can occur when there is no remote contents
+ return new ByteArrayInputStream(new byte[0]);
+ }
+
+ protected File getFile() {
+ return new File(cache.getCachePath().toFile(), filePath);
+ }
+
+ /**
+ * Set the contents of for this cache entry. This method supports concurrency by only allowing
+ * one cache entry to be written at a time. In the case of two concurrent writes to the same cache entry,
+ * the contents from the first write is used and the content from subsequent writes is ignored.
+ * @param stream an InputStream that provides the contents to be cached
+ * @param monitor a progress monitor
+ * @throws TeamException if the entry is DISPOSED or an I/O error occurres
+ */
+ public void setContents(InputStream stream, IProgressMonitor monitor) throws TeamException {
+ // Use a lock to only allow one write at a time
+ beginOperation();
+ try {
+ internalSetContents(stream, monitor);
+ } finally {
+ endOperation();
+ }
+ }
+
+ private synchronized void endOperation() {
+ lock.release();
+ }
+
+ private synchronized void beginOperation() {
+ lock.acquire();
+ }
+
+ private void internalSetContents(InputStream stream, IProgressMonitor monitor) throws TeamException {
+ // if the state is DISPOSED then there is a problem
+ if (state == DISPOSED) {
+ throw new TeamException(Policy.bind("RemoteContentsCacheEntry.3", cache.getName(), id)); //$NON-NLS-1$
+ }
+ // Otherwise, the state is UNINITIALIZED or READY so we can proceed
+ registerHit();
+ File ioFile = getFile();
+ try {
+
+ // Open the cache file for writing
+ OutputStream out;
+ try {
+ if (state == UNINITIALIZED) {
+ out = new BufferedOutputStream(new FileOutputStream(ioFile));
+ } else {
+ // If the entry is READY, the contents must have been read in another thread.
+ // We still need to red the contents but they can be ignored since presumably they are the same
+ out = new ByteArrayOutputStream();
+ }
+ } catch (FileNotFoundException e) {
+ throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$
+ }
+
+ // Transfer the contents
+ try {
+ try {
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = stream.read(buffer)) >= 0) {
+ Policy.checkCanceled(monitor);
+ out.write(buffer, 0, read);
+ }
+ } finally {
+ out.close();
+ }
+ } catch (IOException e) {
+ // Make sure we don't leave the cache file around as it may not have the right contents
+ cache.purgeFromCache(this);
+ throw e;
+ }
+
+ // Mark the cache entry as ready
+ state = READY;
+ } catch (IOException e) {
+ throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException e1) {
+ // Ignore close errors
+ }
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ICacheEntry#getState()
+ */
+ public int getState() {
+ return state;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ICacheEntry#getSize()
+ */
+ public long getSize() {
+ if (state != READY) return 0;
+ File ioFile = getFile();
+ if (ioFile.exists()) {
+ return ioFile.length();
+ }
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ICacheEntry#getLastAccessTimeStamp()
+ */
+ public long getLastAccessTimeStamp() {
+ return lastAccess;
+ }
+
+ /**
+ * Registers a hit on this cache entry. This updates the last access timestamp.
+ * Thsi method is intended to only be invokded from inside this class or the cahce itself.
+ * Other clients should not use it.
+ */
+ protected void registerHit() {
+ lastAccess = new Date().getTime();
+ }
+
+ public void dispose() {
+ // Use a lock to avoid changing state while another thread may be writting
+ beginOperation();
+ try {
+ state = DISPOSED;
+ cache.purgeFromCache(this);
+ } finally {
+ endOperation();
+ }
+ }
+
+
+ public String getId() {
+ return id;
+ }
+
+ public CachedResourceVariant getResourceVariant() {
+ return resourceVariant;
+ }
+
+ public void setResourceVariant(CachedResourceVariant resourceVariant) {
+ this.resourceVariant = resourceVariant;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/Sorter.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/Sorter.java
new file mode 100644
index 000000000..475370c94
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/Sorter.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * 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.team.internal.core;
+
+/**
+ * The SortOperation takes a collection of objects and returns
+ * a sorted collection of these objects. Concrete instances of this
+ * class provide the criteria for the sorting of the objects based on
+ * the type of the objects.
+ */
+public abstract class Sorter {
+ /**
+ * Returns true is elementTwo is 'greater than' elementOne
+ * This is the 'ordering' method of the sort operation.
+ * Each subclass overides this method with the particular
+ * implementation of the 'greater than' concept for the
+ * objects being sorted.
+ */
+ public abstract boolean compare(Object elementOne, Object elementTwo);
+ /**
+ * Sort the objects in sorted collection and return that collection.
+ */
+ private Object[] quickSort(Object[] sortedCollection, int left, int right) {
+ int originalLeft = left;
+ int originalRight = right;
+ Object mid = sortedCollection[ (left + right) / 2];
+ do {
+ while (compare(sortedCollection[left], mid))
+ left++;
+ while (compare(mid, sortedCollection[right]))
+ right--;
+ if (left <= right) {
+ Object tmp = sortedCollection[left];
+ sortedCollection[left] = sortedCollection[right];
+ sortedCollection[right] = tmp;
+ left++;
+ right--;
+ }
+ } while (left <= right);
+ if (originalLeft < right)
+ sortedCollection = quickSort(sortedCollection, originalLeft, right);
+ if (left < originalRight)
+ sortedCollection = quickSort(sortedCollection, left, originalRight);
+ return sortedCollection;
+ }
+ /**
+ * Return a new sorted collection from this unsorted collection.
+ * Sort using quick sort.
+ */
+ public Object[] sort(Object[] unSortedCollection) {
+ int size = unSortedCollection.length;
+ Object[] sortedCollection = new Object[size];
+ //copy the array so can return a new sorted collection
+ System.arraycopy(unSortedCollection, 0, sortedCollection, 0, size);
+ if (size > 1)
+ quickSort(sortedCollection, 0, size - 1);
+ return sortedCollection;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/TeamPlugin.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/TeamPlugin.java
index 5b5d9140f..d646167ff 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/TeamPlugin.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/TeamPlugin.java
@@ -71,6 +71,7 @@ final public class TeamPlugin extends Plugin {
*/
public void shutdown() {
Team.shutdown();
+ ResourceVariantCache.shutdown();
}
/**
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/XMLMemento.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/XMLMemento.java
new file mode 100644
index 000000000..17a0bd383
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/XMLMemento.java
@@ -0,0 +1,406 @@
+/*******************************************************************************
+ * 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.team.internal.core;
+
+import java.io.*;
+import java.util.ArrayList;
+
+import javax.xml.parsers.*;
+import javax.xml.transform.*;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.team.core.TeamException;
+import org.w3c.dom.*;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * This class represents the default implementation of the
+ * <code>IMemento</code> interface.
+ * <p>
+ * This class is not intended to be extended by clients.
+ * </p>
+ * [Note: This class has been copied from org.eclipse.ui to get a quick and
+ * dirty xml input/output code. This should be purged once the settings
+ * work is complete]
+ * @see IMemento
+ */
+public final class XMLMemento implements IMemento {
+ private Document factory;
+ private Element element;
+
+ /**
+ * Creates a <code>Document</code> from the <code>Reader</code>
+ * and returns a memento on the first <code>Element</code> for reading
+ * the document.
+ * <p>
+ * Same as calling createReadRoot(reader, null)
+ * </p>
+ *
+ * @param reader the <code>Reader</code> used to create the memento's document
+ * @return a memento on the first <code>Element</code> for reading the document
+ * @throws <code>WorkbenchException</code> if IO problems, invalid format, or no element.
+ */
+ public static XMLMemento createReadRoot(Reader reader) throws TeamException {
+ return createReadRoot(reader, null);
+ }
+
+ /**
+ * Creates a <code>Document</code> from the <code>Reader</code>
+ * and returns a memento on the first <code>Element</code> for reading
+ * the document.
+ *
+ * @param reader the <code>Reader</code> used to create the memento's document
+ * @param baseDir the directory used to resolve relative file names
+ * in the XML document. This directory must exist and include the
+ * trailing separator. The directory format, including the separators,
+ * must be valid for the platform. Can be <code>null</code> if not
+ * needed.
+ * @return a memento on the first <code>Element</code> for reading the document
+ * @throws <code>WorkbenchException</code> if IO problems, invalid format, or no element.
+ */
+ public static XMLMemento createReadRoot(Reader reader, String baseDir) throws TeamException {
+ String messageKey = "XMLMemento.noElement"; //$NON-NLS-1$
+ Exception exception = null;
+
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder parser = factory.newDocumentBuilder();
+ InputSource source = new InputSource(reader);
+ if (baseDir != null)
+ source.setSystemId(baseDir);
+ Document document = parser.parse(source);
+ NodeList list = document.getChildNodes();
+ for (int i = 0; i < list.getLength(); i++) {
+ Node node = list.item(i);
+ if (node instanceof Element)
+ return new XMLMemento(document, (Element) node);
+ }
+ } catch (ParserConfigurationException e) {
+ exception = e;
+ messageKey = "XMLMemento.parserConfigError"; //$NON-NLS-1$
+ } catch (IOException e) {
+ exception = e;
+ messageKey = "XMLMemento.ioError"; //$NON-NLS-1$
+ } catch (SAXException e) {
+ exception = e;
+ messageKey = "XMLMemento.formatError"; //$NON-NLS-1$
+ }
+
+ String problemText = null;
+ if (exception != null)
+ problemText = exception.getMessage();
+ //if (problemText == null || problemText.length() == 0)
+ // problemText = TeamException.getString(messageKey);
+ throw new TeamException(problemText);
+ }
+
+ /**
+ * Returns a root memento for writing a document.
+ *
+ * @param type the element node type to create on the document
+ * @return the root memento for writing a document
+ */
+ public static XMLMemento createWriteRoot(String type) {
+ Document document;
+ try {
+ document = DocumentBuilderFactory
+ .newInstance()
+ .newDocumentBuilder()
+ .newDocument();
+ Element element = document.createElement(type);
+ document.appendChild(element);
+ return new XMLMemento(document, element);
+ }
+ catch (ParserConfigurationException e) {
+ throw new Error(e);
+ }
+ }
+
+ /**
+ * Creates a memento for the specified document and element.
+ * <p>
+ * Clients should use <code>createReadRoot</code> and
+ * <code>createWriteRoot</code> to create the initial
+ * memento on a document.
+ * </p>
+ *
+ * @param document the document for the memento
+ * @param element the element node for the memento
+ */
+ public XMLMemento(Document document, Element element) {
+ super();
+ this.factory = document;
+ this.element = element;
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public IMemento createChild(String type) {
+ Element child = factory.createElement(type);
+ element.appendChild(child);
+ return new XMLMemento(factory, child);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public IMemento createChild(String type, String id) {
+ Element child = factory.createElement(type);
+ child.setAttribute(TAG_ID, id == null ? "" : id); //$NON-NLS-1$
+ element.appendChild(child);
+ return new XMLMemento(factory, child);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public IMemento copyChild(IMemento child) {
+ Element childElement = ((XMLMemento) child).element;
+ Element newElement = (Element) factory.importNode(childElement, true);
+ element.appendChild(newElement);
+ return new XMLMemento(factory, newElement);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public IMemento getChild(String type) {
+
+ // Get the nodes.
+ NodeList nodes = element.getChildNodes();
+ int size = nodes.getLength();
+ if (size == 0)
+ return null;
+
+ // Find the first node which is a child of this node.
+ for (int nX = 0; nX < size; nX++) {
+ Node node = nodes.item(nX);
+ if (node instanceof Element) {
+ Element element = (Element) node;
+ if (element.getNodeName().equals(type))
+ return new XMLMemento(factory, element);
+ }
+ }
+
+ // A child was not found.
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public IMemento[] getChildren(String type) {
+
+ // Get the nodes.
+ NodeList nodes = element.getChildNodes();
+ int size = nodes.getLength();
+ if (size == 0)
+ return new IMemento[0];
+
+ // Extract each node with given type.
+ ArrayList list = new ArrayList(size);
+ for (int nX = 0; nX < size; nX++) {
+ Node node = nodes.item(nX);
+ if (node instanceof Element) {
+ Element element = (Element) node;
+ if (element.getNodeName().equals(type))
+ list.add(element);
+ }
+ }
+
+ // Create a memento for each node.
+ size = list.size();
+ IMemento[] results = new IMemento[size];
+ for (int x = 0; x < size; x++) {
+ results[x] = new XMLMemento(factory, (Element) list.get(x));
+ }
+ return results;
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public Float getFloat(String key) {
+ Attr attr = element.getAttributeNode(key);
+ if (attr == null)
+ return null;
+ String strValue = attr.getValue();
+ try {
+ return new Float(strValue);
+ } catch (NumberFormatException e) {
+ TeamPlugin.log(IStatus.ERROR, "Memento problem - Invalid float for key: " //$NON-NLS-1$
+ + key + " value: " + strValue, null); //$NON-NLS-1$
+ return null;
+ }
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public String getID() {
+ return element.getAttribute(TAG_ID);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public Integer getInteger(String key) {
+ Attr attr = element.getAttributeNode(key);
+ if (attr == null)
+ return null;
+ String strValue = attr.getValue();
+ try {
+ return new Integer(strValue);
+ } catch (NumberFormatException e) {
+ TeamPlugin.log(IStatus.ERROR, "Memento problem - invalid integer for key: " + key //$NON-NLS-1$
+ + " value: " + strValue, null); //$NON-NLS-1$
+ return null;
+ }
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public String getString(String key) {
+ Attr attr = element.getAttributeNode(key);
+ if (attr == null)
+ return null;
+ return attr.getValue();
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public String getTextData() {
+ Text textNode = getTextNode();
+ if (textNode != null) {
+ return textNode.getData();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the Text node of the memento. Each memento is allowed only
+ * one Text node.
+ *
+ * @return the Text node of the memento, or <code>null</code> if
+ * the memento has no Text node.
+ */
+ private Text getTextNode() {
+ // Get the nodes.
+ NodeList nodes = element.getChildNodes();
+ int size = nodes.getLength();
+ if (size == 0)
+ return null;
+ for (int nX = 0; nX < size; nX++) {
+ Node node = nodes.item(nX);
+ if (node instanceof Text) {
+ return (Text) node;
+ }
+ }
+ // a Text node was not found
+ return null;
+ }
+
+ /**
+ * Places the element's attributes into the document.
+ */
+ private void putElement(Element element) {
+ NamedNodeMap nodeMap = element.getAttributes();
+ int size = nodeMap.getLength();
+ for (int i = 0; i < size; i++) {
+ Attr attr = (Attr) nodeMap.item(i);
+ putString(attr.getName(), attr.getValue());
+ }
+
+ NodeList nodes = element.getChildNodes();
+ size = nodes.getLength();
+ for (int i = 0; i < size; i++) {
+ Node node = nodes.item(i);
+ if (node instanceof Element) {
+ XMLMemento child = (XMLMemento) createChild(node.getNodeName());
+ child.putElement((Element) node);
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public void putFloat(String key, float f) {
+ element.setAttribute(key, String.valueOf(f));
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public void putInteger(String key, int n) {
+ element.setAttribute(key, String.valueOf(n));
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public void putMemento(IMemento memento) {
+ putElement(((XMLMemento) memento).element);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public void putString(String key, String value) {
+ if (value == null)
+ return;
+ element.setAttribute(key, value);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public void putTextData(String data) {
+ Text textNode = getTextNode();
+ if (textNode == null) {
+ textNode = factory.createTextNode(data);
+ element.appendChild(textNode);
+ } else {
+ textNode.setData(data);
+ }
+ }
+
+ /**
+ * Saves this memento's document current values to the
+ * specified writer.
+ *
+ * @param writer the writer used to save the memento's document
+ * @throws IOException if there is a problem serializing the document to the stream.
+ */
+ public void save(Writer writer) throws IOException {
+ Result result = new StreamResult(writer);
+ Source source = new DOMSource(factory);
+ try {
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
+ transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
+ transformer.transform(source, result);
+ }
+ catch (TransformerConfigurationException e) {
+ throw (IOException) (new IOException().initCause(e));
+ }
+ catch (TransformerException e) {
+ throw (IOException) (new IOException().initCause(e));
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/messages.properties b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/messages.properties
index 4dc695713..e55b417f1 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/messages.properties
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/messages.properties
@@ -105,3 +105,21 @@ TeamProvider.10=Error restoring subscribers. Cannot find factory with id: {0}
TeamProvider.11=Error saving subscribers. Cannot find factory with id: {0}
ContentComparisonCriteria.2=Comparing content {0}
ContentComparisonCriteria.3=\ ignoring whitespace
+
+SubscriberEventHandler.2=Calculating synchronization state for {0}.
+SubscriberEventHandler.jobName=Updating synchronization states for {0}.
+SubscriberEventHandler.errors=Errors have occured while calculating the synchronization state for {0}.
+RemoteContentsCacheEntry.3=Cache entry in {0} for {1} has been disposed
+DeploymentProviderManager.10=Cannot map provider {0}. It's extension point description cannot be found.
+DeploymentProviderManager.12=Resource {0} is mapped to multiple deployment providers of type {1}.
+DeploymentProviderManager.13={0} is already mapped to {1}
+DeploymentProviderManager.15=An I/O error occurred while persisting the deployment configurations for project {0}.
+DeploymentProviderManager.16=Previously deployed folder {0} in project {1} no longer exists.
+DeploymentProviderManager.17=Previously deployed resource {0} in project {1} is now a file and cannot be deployed.
+SynchronizationCacheRefreshOperation.0=Refreshing {0}
+SubscriberEventHandler.8=The members of folder {0} could not be retrieved: {1}
+SubscriberEventHandler.9=The synchronization state for resource {0} could not be determined: {1}
+SubscriberEventHandler.10=An internal error occurred processing subscriber events.
+SubscriberEventHandler.11=An internal error occurred processing resource {0}: {1}
+CachedResourceVariant.0=There is no cached contents for resource {0}.
+SyncInfoTree.0=Sync info is missing for resource {0}.
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderDescriptor.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderDescriptor.java
new file mode 100644
index 000000000..980c73871
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderDescriptor.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * 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.team.internal.core.registry;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.core.DeploymentProvider;
+
+public class DeploymentProviderDescriptor {
+
+ public static final String ATT_ID = "id"; //$NON-NLS-1$
+ public static final String ATT_NAME = "name"; //$NON-NLS-1$
+ public static final String ATT_CLASS = "class"; //$NON-NLS-1$
+
+ private String name;
+ private String className;
+ private String id;
+ private String description;
+
+ private IConfigurationElement configElement;
+
+ /**
+ * Create a new ViewDescriptor for an extension.
+ */
+ public DeploymentProviderDescriptor(IConfigurationElement e, String desc) throws CoreException {
+ configElement = e;
+ description = desc;
+ loadFromExtension();
+ }
+
+
+ public IConfigurationElement getConfigurationElement() {
+ return configElement;
+ }
+
+ public DeploymentProvider createProvider() throws CoreException {
+ return (DeploymentProvider)configElement.createExecutableExtension(ATT_CLASS);
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ private void loadFromExtension() throws CoreException {
+ String identifier = configElement.getAttribute(ATT_ID);
+ name = configElement.getAttribute(ATT_NAME);
+ className = configElement.getAttribute(ATT_CLASS);
+
+ // Sanity check.
+ if ((name == null) || (className == null) || (identifier == null)) {
+ throw new CoreException(new Status(IStatus.ERROR, configElement.getDeclaringExtension().getDeclaringPluginDescriptor().getUniqueIdentifier(), 0, "Invalid extension (missing label or class name): " + id, //$NON-NLS-1$
+ null));
+ }
+
+ id = identifier;
+ }
+
+ /**
+ * Returns a string representation of this descriptor. For debugging
+ * purposes only.
+ */
+ public String toString() {
+ return "Team Provider(" + getId() + ")"; //$NON-NLS-2$ //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderRegistry.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderRegistry.java
new file mode 100644
index 000000000..09beff082
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderRegistry.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.team.internal.core.registry;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.internal.core.TeamPlugin;
+
+public class DeploymentProviderRegistry extends RegistryReader {
+
+ private final static String PT_TEAMPROVIDER = "deployment"; //$NON-NLS-1$
+ private Map providers = new HashMap();
+ private String extensionId;
+
+ public DeploymentProviderRegistry() {
+ super();
+ this.extensionId = PT_TEAMPROVIDER;
+ readRegistry(Platform.getPluginRegistry(), TeamPlugin.ID, PT_TEAMPROVIDER);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.registry.RegistryReader#readElement(org.eclipse.core.runtime.IConfigurationElement)
+ */
+ protected boolean readElement(IConfigurationElement element) {
+ if (element.getName().equals(extensionId)) {
+ String descText = getDescription(element);
+ DeploymentProviderDescriptor desc;
+ try {
+ desc = new DeploymentProviderDescriptor(element, descText);
+ providers.put(desc.getId(), desc);
+ } catch (CoreException e) {
+ TeamPlugin.log(e);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public DeploymentProviderDescriptor[] getTeamProviderDescriptors() {
+ return (DeploymentProviderDescriptor[])providers.values().toArray(new DeploymentProviderDescriptor[providers.size()]);
+ }
+
+ public DeploymentProviderDescriptor find(String id) {
+ return (DeploymentProviderDescriptor)providers.get(id);
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/RegistryReader.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/RegistryReader.java
new file mode 100644
index 000000000..afb5dbef1
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/RegistryReader.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * 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.team.internal.core.registry;
+
+import java.util.Hashtable;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.internal.core.Sorter;
+import org.eclipse.team.internal.core.TeamPlugin;
+
+public abstract class RegistryReader {
+ protected static final String TAG_DESCRIPTION = "description"; //$NON-NLS-1$
+ protected static Hashtable extensionPoints = new Hashtable();
+ /**
+ * The constructor.
+ */
+ protected RegistryReader() {
+ }
+ /**
+ * This method extracts description as a subelement of the given element.
+ *
+ * @return description string if defined, or empty string if not.
+ */
+ protected String getDescription(IConfigurationElement config) {
+ IConfigurationElement[] children = config.getChildren(TAG_DESCRIPTION);
+ if (children.length >= 1) {
+ return children[0].getValue();
+ }
+ return ""; //$NON-NLS-1$
+ }
+ /**
+ * Logs the error in the workbench log using the provided text and the
+ * information in the configuration element.
+ */
+ protected void logError(IConfigurationElement element, String text) {
+ IExtension extension = element.getDeclaringExtension();
+ IPluginDescriptor descriptor = extension.getDeclaringPluginDescriptor();
+ StringBuffer buf = new StringBuffer();
+ buf.append("Plugin " + descriptor.getUniqueIdentifier() + ", extension " + extension.getExtensionPointUniqueIdentifier()); //$NON-NLS-2$//$NON-NLS-1$
+ buf.append("\n" + text); //$NON-NLS-1$
+ TeamPlugin.log(IStatus.ERROR, buf.toString(), null);
+ }
+ /**
+ * Logs a very common registry error when a required attribute is missing.
+ */
+ protected void logMissingAttribute(IConfigurationElement element, String attributeName) {
+ logError(element, "Required attribute '" + attributeName + "' not defined"); //$NON-NLS-2$//$NON-NLS-1$
+ }
+
+ /**
+ * Logs a very common registry error when a required child is missing.
+ */
+ protected void logMissingElement(IConfigurationElement element, String elementName) {
+ logError(element, "Required sub element '" + elementName + "' not defined"); //$NON-NLS-2$//$NON-NLS-1$
+ }
+
+ /**
+ * Logs a registry error when the configuration element is unknown.
+ */
+ protected void logUnknownElement(IConfigurationElement element) {
+ logError(element, "Unknown extension tag found: " + element.getName()); //$NON-NLS-1$
+ }
+ /**
+ * Apply a reproducable order to the list of extensions provided, such that
+ * the order will not change as extensions are added or removed.
+ */
+ protected IExtension[] orderExtensions(IExtension[] extensions) {
+ // By default, the order is based on plugin id sorted
+ // in ascending order. The order for a plugin providing
+ // more than one extension for an extension point is
+ // dependent in the order listed in the XML file.
+ Sorter sorter = new Sorter() {
+ public boolean compare(Object extension1, Object extension2) {
+ String s1 = ((IExtension) extension1).getDeclaringPluginDescriptor().getUniqueIdentifier();
+ String s2 = ((IExtension) extension2).getDeclaringPluginDescriptor().getUniqueIdentifier();
+ //Return true if elementTwo is 'greater than' elementOne
+ return s2.compareToIgnoreCase(s1) > 0;
+ }
+ };
+
+ Object[] sorted = sorter.sort(extensions);
+ IExtension[] sortedExtension = new IExtension[sorted.length];
+ System.arraycopy(sorted, 0, sortedExtension, 0, sorted.length);
+ return sortedExtension;
+ }
+ /**
+ * Implement this method to read element's attributes. If children should
+ * also be read, then implementor is responsible for calling <code>readElementChildren</code>.
+ * Implementor is also responsible for logging missing attributes.
+ *
+ * @return true if element was recognized, false if not.
+ */
+ protected abstract boolean readElement(IConfigurationElement element);
+ /**
+ * Read the element's children. This is called by the subclass' readElement
+ * method when it wants to read the children of the element.
+ */
+ protected void readElementChildren(IConfigurationElement element) {
+ readElements(element.getChildren());
+ }
+ /**
+ * Read each element one at a time by calling the subclass implementation
+ * of <code>readElement</code>.
+ *
+ * Logs an error if the element was not recognized.
+ */
+ protected void readElements(IConfigurationElement[] elements) {
+ for (int i = 0; i < elements.length; i++) {
+ if (!readElement(elements[i]))
+ logUnknownElement(elements[i]);
+ }
+ }
+ /**
+ * Read one extension by looping through its configuration elements.
+ */
+ protected void readExtension(IExtension extension) {
+ readElements(extension.getConfigurationElements());
+ }
+ /**
+ * Start the registry reading process using the supplied plugin ID and
+ * extension point.
+ */
+ public void readRegistry(IPluginRegistry registry, String pluginId, String extensionPoint) {
+ String pointId = pluginId + "-" + extensionPoint; //$NON-NLS-1$
+ IExtension[] extensions = (IExtension[]) extensionPoints.get(pointId);
+ if (extensions == null) {
+ IExtensionPoint point = registry.getExtensionPoint(pluginId, extensionPoint);
+ if (point == null)
+ return;
+ extensions = point.getExtensions();
+ extensionPoints.put(pointId, extensions);
+ }
+ for (int i = 0; i < extensions.length; i++)
+ readExtension(extensions[i]);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/ContentComparator.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/ContentComparator.java
new file mode 100644
index 000000000..44922f9fa
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/ContentComparator.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers;
+
+import java.io.*;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.IResourceVariant;
+import org.eclipse.team.internal.core.Policy;
+import org.eclipse.team.internal.core.TeamPlugin;
+
+/**
+ * This is an internal class that is usd by the <code>ContentComparisonSyncInfoFilter</code>
+ * to compare the comtents of the local and remote resources
+ */
+public class ContentComparator {
+
+ private boolean ignoreWhitespace = false;
+
+ public ContentComparator(boolean ignoreWhitespace) {
+ this.ignoreWhitespace = ignoreWhitespace;
+ }
+
+ public boolean compare(Object e1, Object e2, IProgressMonitor monitor) {
+ InputStream is1 = null;
+ InputStream is2 = null;
+ try {
+ is1 = getContents(e1, Policy.subMonitorFor(monitor, 50));
+ is2 = getContents(e2, Policy.subMonitorFor(monitor, 50));
+ return contentsEqual(is1, is2, shouldIgnoreWhitespace());
+ } catch(TeamException e) {
+ TeamPlugin.log(e);
+ return false;
+ } finally {
+ try {
+ try {
+ if (is1 != null) {
+ is1.close();
+ }
+ } finally {
+ if (is2 != null) {
+ is2.close();
+ }
+ }
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+
+ protected boolean shouldIgnoreWhitespace() {
+ return ignoreWhitespace;
+ }
+
+ /**
+ * Returns <code>true</code> if both input streams byte contents is
+ * identical.
+ *
+ * @param input1
+ * first input to contents compare
+ * @param input2
+ * second input to contents compare
+ * @return <code>true</code> if content is equal
+ */
+ private boolean contentsEqual(InputStream is1, InputStream is2, boolean ignoreWhitespace) {
+ try {
+ if (is1 == is2)
+ return true;
+
+ if (is1 == null && is2 == null) // no byte contents
+ return true;
+
+ if (is1 == null || is2 == null) // only one has
+ // contents
+ return false;
+
+ while (true) {
+ int c1 = is1.read();
+ while (shouldIgnoreWhitespace() && isWhitespace(c1))
+ c1 = is1.read();
+ int c2 = is2.read();
+ while (shouldIgnoreWhitespace() && isWhitespace(c2))
+ c2 = is2.read();
+ if (c1 == -1 && c2 == -1)
+ return true;
+ if (c1 != c2)
+ break;
+
+ }
+ } catch (IOException ex) {
+ } finally {
+ try {
+ try {
+ if (is1 != null) {
+ is1.close();
+ }
+ } finally {
+ if (is2 != null) {
+ is2.close();
+ }
+ }
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ return false;
+ }
+
+ private boolean isWhitespace(int c) {
+ if (c == -1)
+ return false;
+ return Character.isWhitespace((char) c);
+ }
+
+ private InputStream getContents(Object resource, IProgressMonitor monitor) throws TeamException {
+ try {
+ if (resource instanceof IFile) {
+ return new BufferedInputStream(((IFile) resource).getContents());
+ } else if(resource instanceof IResourceVariant) {
+ IResourceVariant remote = (IResourceVariant)resource;
+ if (!remote.isContainer()) {
+ return new BufferedInputStream(remote.getStorage(monitor).getContents());
+ }
+ }
+ return null;
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberEventHandler.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberEventHandler.java
new file mode 100644
index 000000000..faebc4de7
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberEventHandler.java
@@ -0,0 +1,431 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers;
+
+import java.util.*;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.team.core.*;
+import org.eclipse.team.core.subscribers.Subscriber;
+import org.eclipse.team.core.synchronize.SyncInfo;
+import org.eclipse.team.internal.core.*;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * This handler collects changes and removals to resources and calculates their
+ * synchronization state in a background job. The result is fed input the SyncSetInput.
+ *
+ * Exceptions that occur when the job is processing the events are collected and
+ * returned as part of the Job's status.
+ */
+public class SubscriberEventHandler extends BackgroundEventHandler {
+ // The set that receives notification when the resource synchronization state
+ // has been calculated by the job.
+ private SyncSetInputFromSubscriber syncSetInput;
+
+ // Changes accumulated by the event handler
+ private List resultCache = new ArrayList();
+
+ private boolean started = false;
+
+ private IProgressMonitor progressGroup;
+
+ private int ticks;
+
+ /**
+ * Internal resource synchronization event. Can contain a result.
+ */
+ class SubscriberEvent extends Event{
+ static final int REMOVAL = 1;
+ static final int CHANGE = 2;
+ static final int INITIALIZE = 3;
+ SyncInfo result;
+
+ SubscriberEvent(IResource resource, int type, int depth) {
+ super(resource, type, depth);
+ }
+ public SubscriberEvent(
+ IResource resource,
+ int type,
+ int depth,
+ SyncInfo result) {
+ this(resource, type, depth);
+ this.result = result;
+ }
+ public SyncInfo getResult() {
+ return result;
+ }
+ protected String getTypeString() {
+ switch (getType()) {
+ case REMOVAL :
+ return "REMOVAL"; //$NON-NLS-1$
+ case CHANGE :
+ return "CHANGE"; //$NON-NLS-1$
+ case INITIALIZE :
+ return "INITIALIZE"; //$NON-NLS-1$
+ default :
+ return "INVALID"; //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * This is a special event used to reset and connect sync sets.
+ * The preemtive flag is used to indicate that the runnable should take
+ * the highest priority and thus be placed on the front of the queue
+ * and be processed as soon as possible, preemting any event that is currently
+ * being processed. The curent event will continue processing once the
+ * high priority event has been processed
+ */
+ public class RunnableEvent extends Event {
+ static final int RUNNABLE = 1000;
+ private IWorkspaceRunnable runnable;
+ private boolean preemtive;
+ public RunnableEvent(IWorkspaceRunnable runnable, boolean preemtive) {
+ super(ResourcesPlugin.getWorkspace().getRoot(), RUNNABLE, IResource.DEPTH_ZERO);
+ this.runnable = runnable;
+ this.preemtive = preemtive;
+ }
+ public void run(IProgressMonitor monitor) throws CoreException {
+ runnable.run(monitor);
+ }
+ public boolean isPreemtive() {
+ return preemtive;
+ }
+ }
+
+ /**
+ * Create a handler. This will initialize all resources for the subscriber associated with
+ * the set.
+ * @param set the subscriber set to feed changes into
+ */
+ public SubscriberEventHandler(Subscriber subscriber) {
+ super(
+ Policy.bind("SubscriberEventHandler.jobName", subscriber.getName()), //$NON-NLS-1$
+ Policy.bind("SubscriberEventHandler.errors", subscriber.getName())); //$NON-NLS-1$
+ this.syncSetInput = new SyncSetInputFromSubscriber(subscriber, this);
+ }
+
+ /**
+ * Start the event handler by queuing events to prime the sync set input with the out-of-sync
+ * resources of the subscriber.
+ */
+ public synchronized void start() {
+ // Set the started flag to enable event queueing.
+ // We are gaurenteed to be the first since this method is synchronized.
+ started = true;
+ reset(syncSetInput.getSubscriber().roots(), SubscriberEvent.INITIALIZE);
+ }
+
+ protected synchronized void queueEvent(Event event, boolean front) {
+ // Only post events if the handler is started
+ if (started) {
+ super.queueEvent(event, front);
+ }
+ }
+ /**
+ * Schedule the job or process the events now.
+ */
+ public void schedule() {
+ Job job = getEventHandlerJob();
+ if(progressGroup != null) {
+ job.setSystem(false);
+ job.setProgressGroup(progressGroup, ticks);
+ } else {
+ job.setSystem(true);
+ }
+ getEventHandlerJob().schedule();
+ }
+
+ /**
+ * Initialize all resources for the subscriber associated with the set. This will basically recalculate
+ * all synchronization information for the subscriber.
+ * <p>
+ * This method is sycnrhonized with the queueEvent method to ensure that the two events
+ * queued by this method are back-to-back
+ */
+ public synchronized void reset(IResource[] roots) {
+ if (roots == null) {
+ roots = syncSetInput.getSubscriber().roots();
+ }
+ // First, reset the sync set input to clear the sync set
+ run(new IWorkspaceRunnable() {
+ public void run(IProgressMonitor monitor) throws CoreException {
+ syncSetInput.reset(monitor);
+ }
+ }, false /* keep ordering the same */);
+ // Then, prime the set from the subscriber
+ reset(roots, SubscriberEvent.CHANGE);
+ }
+
+ /**
+ * Called by a client to indicate that a resource has changed and its synchronization state
+ * should be recalculated.
+ * @param resource the changed resource
+ * @param depth the depth of the change calculation
+ */
+ public void change(IResource resource, int depth) {
+ queueEvent(new SubscriberEvent(resource, SubscriberEvent.CHANGE, depth), false);
+ }
+
+ /**
+ * Called by a client to indicate that a resource has been removed and should be removed. The
+ * removal will propagate to the set.
+ * @param resource the resource that was removed
+ */
+ public void remove(IResource resource) {
+ queueEvent(
+ new SubscriberEvent(resource, SubscriberEvent.REMOVAL, IResource.DEPTH_INFINITE), false);
+ }
+
+ /**
+ * Collect the calculated synchronization information for the given resource at the given depth. The
+ * results are added to the provided list.
+ */
+ private void collect(
+ IResource resource,
+ int depth,
+ IProgressMonitor monitor) {
+
+ // handle any preemtive events before continuing
+ handlePreemptiveEvents(monitor);
+
+ if (resource.getType() != IResource.FILE
+ && depth != IResource.DEPTH_ZERO) {
+ try {
+ IResource[] members =
+ syncSetInput.getSubscriber().members(resource);
+ for (int i = 0; i < members.length; i++) {
+ collect(
+ members[i],
+ depth == IResource.DEPTH_INFINITE
+ ? IResource.DEPTH_INFINITE
+ : IResource.DEPTH_ZERO,
+ monitor);
+ }
+ } catch (TeamException e) {
+ handleException(e, resource, ITeamStatus.SYNC_INFO_SET_ERROR, Policy.bind("SubscriberEventHandler.8", resource.getFullPath().toString(), e.getMessage())); //$NON-NLS-1$
+ }
+ }
+
+ monitor.subTask(Policy.bind("SubscriberEventHandler.2", resource.getFullPath().toString())); //$NON-NLS-1$
+ try {
+ SyncInfo info = syncSetInput.getSubscriber().getSyncInfo(resource);
+ // resource is no longer under the subscriber control
+ if (info == null) {
+ resultCache.add(
+ new SubscriberEvent(resource, SubscriberEvent.REMOVAL, IResource.DEPTH_ZERO));
+ } else {
+ resultCache.add(
+ new SubscriberEvent(resource, SubscriberEvent.CHANGE, IResource.DEPTH_ZERO, info));
+ }
+ handlePendingDispatch(monitor);
+ } catch (TeamException e) {
+ handleException(e, resource, ITeamStatus.RESOURCE_SYNC_INFO_ERROR, Policy.bind("SubscriberEventHandler.9", resource.getFullPath().toString(), e.getMessage())); //$NON-NLS-1$
+ }
+ monitor.worked(1);
+ }
+
+ private void handlePendingDispatch(IProgressMonitor monitor) {
+ if (isReadyForDispatch(false /*don't wait if queue is empty*/)) {
+ dispatchEvents(Policy.subMonitorFor(monitor, 5));
+ eventsDispatched();
+ }
+ }
+
+ /*
+ * Handle the exception by returning it as a status from the job but also by
+ * dispatching it to the sync set input so any down stream views can react
+ * accordingly.
+ */
+ private void handleException(CoreException e, IResource resource, int code, String message) {
+ handleException(e);
+ syncSetInput.handleError(new TeamStatus(IStatus.ERROR, TeamPlugin.ID, code, message, e, resource));
+ }
+
+ /**
+ * Called to initialize to calculate the synchronization information using the optimized subscriber method. For
+ * subscribers that don't support the optimization, all resources in the subscriber are manually re-calculated.
+ * @param resources the resources to check
+ * @param depth the depth
+ * @param monitor
+ * @return Event[] the change events
+ * @throws TeamException
+ */
+ private void collectAll(
+ IResource resource,
+ int depth,
+ IProgressMonitor monitor) {
+
+ monitor.beginTask(null, 100);
+ try {
+ SyncInfo[] infos = null;
+ try {
+ infos = syncSetInput.getSubscriber().getAllOutOfSync(new IResource[] { resource }, depth, Policy.subMonitorFor(monitor, 10));
+ } catch (TeamException e) {
+ // Log the exception and fallback to using hierarchical search
+ TeamPlugin.log(e);
+ }
+
+ // The subscriber hasn't cached out-of-sync resources. We will have to
+ // traverse all resources and calculate their state.
+ if (infos == null) {
+ IProgressMonitor subMonitor = Policy.infiniteSubMonitorFor(monitor, 90);
+ subMonitor.beginTask(null, 20);
+ collect(
+ resource,
+ IResource.DEPTH_INFINITE,
+ subMonitor);
+ } else {
+ // The subscriber has returned the list of out-of-sync resources.
+ for (int i = 0; i < infos.length; i++) {
+ SyncInfo info = infos[i];
+ resultCache.add(
+ new SubscriberEvent(info.getLocal(), SubscriberEvent.CHANGE, depth, info));
+ }
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Feed the given events to the set. The appropriate method on the set is called
+ * for each event type.
+ * @param events
+ */
+ private void dispatchEvents(SubscriberEvent[] events, IProgressMonitor monitor) {
+ // this will batch the following set changes until endInput is called.
+ try {
+ syncSetInput.getSyncSet().beginInput();
+ for (int i = 0; i < events.length; i++) {
+ SubscriberEvent event = events[i];
+ switch (event.getType()) {
+ case SubscriberEvent.CHANGE :
+ syncSetInput.collect(event.getResult(), monitor);
+ break;
+ case SubscriberEvent.REMOVAL :
+ syncSetInput.getSyncSet().remove(event.getResource(), event.getDepth());
+ break;
+ }
+ }
+ } finally {
+ syncSetInput.getSyncSet().endInput(monitor);
+ }
+ }
+
+ /**
+ * Initialize all resources for the subscriber associated with the set. This will basically recalculate
+ * all synchronization information for the subscriber.
+ * @param type can be Event.CHANGE to recalculate all states or Event.INITIALIZE to perform the
+ * optimized recalculation if supported by the subscriber.
+ */
+ private void reset(IResource[] roots, int type) {
+ IResource[] resources = roots;
+ for (int i = 0; i < resources.length; i++) {
+ queueEvent(new SubscriberEvent(resources[i], type, IResource.DEPTH_INFINITE), false);
+ }
+ }
+
+ protected void processEvent(Event event, IProgressMonitor monitor) {
+ try {
+ // Cancellation is dangerous because this will leave the sync info in a bad state.
+ // Purposely not checking -
+ int type = event.getType();
+ switch (type) {
+ case RunnableEvent.RUNNABLE :
+ executeRunnable(event, monitor);
+ break;
+ case SubscriberEvent.REMOVAL :
+ resultCache.add(event);
+ break;
+ case SubscriberEvent.CHANGE :
+ collect(
+ event.getResource(),
+ event.getDepth(),
+ monitor);
+ break;
+ case SubscriberEvent.INITIALIZE :
+ getEventHandlerJob().setSystem(false);
+ monitor.subTask(Policy.bind("SubscriberEventHandler.2", event.getResource().getFullPath().toString())); //$NON-NLS-1$
+ collectAll(
+ event.getResource(),
+ event.getDepth(),
+ Policy.subMonitorFor(monitor, 64));
+ break;
+ }
+ } catch (RuntimeException e) {
+ // handle the exception and keep processing
+ handleException(new TeamException(Policy.bind("SubscriberEventHandler.10"), e), event.getResource(), ITeamStatus.SYNC_INFO_SET_ERROR, Policy.bind("SubscriberEventHandler.11", event.getResource().getFullPath().toString(), e.getMessage())); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ /*
+ * Execute the RunnableEvent
+ */
+ private void executeRunnable(Event event, IProgressMonitor monitor) {
+ // Dispatch any queued results to clear pending output events
+ dispatchEvents(Policy.subMonitorFor(monitor, 1));
+ eventsDispatched();
+ try {
+ ((RunnableEvent)event).run(Policy.subMonitorFor(monitor, 1));
+ } catch (CoreException e) {
+ handleException(e, event.getResource(), ITeamStatus.SYNC_INFO_SET_ERROR, e.getMessage());
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.subscribers.BackgroundEventHandler#dispatchEvents()
+ */
+ protected void dispatchEvents(IProgressMonitor monitor) {
+ if (!resultCache.isEmpty()) {
+ dispatchEvents((SubscriberEvent[]) resultCache.toArray(new SubscriberEvent[resultCache.size()]), monitor);
+ resultCache.clear();
+ }
+ }
+
+ /**
+ * Queue up the given runnable in an event to be processed by this job
+ * @param runnable the runnable to be run by the handler
+ */
+ public void run(IWorkspaceRunnable runnable, boolean frontOnQueue) {
+ queueEvent(new RunnableEvent(runnable, frontOnQueue), frontOnQueue);
+ }
+
+ /**
+ * Return the sync set input that was created by this event handler
+ * @return
+ */
+ public SyncSetInputFromSubscriber getSyncSetInput() {
+ return syncSetInput;
+ }
+
+ public void setProgressGroupHint(IProgressMonitor progressGroup, int ticks) {
+ this.progressGroup = progressGroup;
+ this.ticks = ticks;
+ }
+
+ /**
+ * @return Returns the started.
+ */
+ protected boolean isStarted() {
+ return started;
+ }
+
+ private void handlePreemptiveEvents(IProgressMonitor monitor) {
+ Event event = peek();
+ if (event instanceof RunnableEvent && ((RunnableEvent)event).isPreemtive()) {
+ executeRunnable(nextElement(), monitor);
+ }
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberSyncInfoSet.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberSyncInfoSet.java
new file mode 100644
index 000000000..99fd3ff20
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberSyncInfoSet.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 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.team.internal.core.subscribers;
+
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener;
+import org.eclipse.team.core.synchronize.SyncInfoTree;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * The <code>SubscriberSyncInfoSet</code> is a <code>SyncInfoSet</code> that provides the ability to add,
+ * remove and change <code>SyncInfo</code> and fires change event notifications to registered listeners.
+ * It also provides the ability
+ * to batch changes in a single change notification as well as optimizations for sync info retrieval.
+ *
+ * This class uses synchronized methods and synchronized blocks to protect internal data structures during both access
+ * and modify operations and uses an <code>ILock</code> to make modification operations thread-safe. The events
+ * are fired while this lock is held so clients responding to these events should not obtain their own internal locks
+ * while processing change events.
+ *
+ * TODO: Override modification methods to enforce use with handler
+ *
+ */
+public class SubscriberSyncInfoSet extends SyncInfoTree {
+
+ protected SubscriberEventHandler handler;
+
+ public SubscriberSyncInfoSet(SubscriberEventHandler handler) {
+ this.handler = handler;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.synchronize.SyncInfoSet#connect(org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void connect(ISyncInfoSetChangeListener listener, IProgressMonitor monitor) {
+ if (handler == null) {
+ super.connect(listener, monitor);
+ } else {
+ connect(listener);
+ }
+ }
+
+ /**
+ * Variation of connect that does not need progress and does not throw an exception.
+ * Progress is provided by the background event handler and errors are passed through
+ * the chain to the view.
+ * @param listener
+ */
+ public void connect(final ISyncInfoSetChangeListener listener) {
+ if (handler == null) {
+ // Should only use this connect if the set has a handler
+ throw new UnsupportedOperationException();
+ } else {
+ handler.run(new IWorkspaceRunnable() {
+ public void run(IProgressMonitor monitor) {
+ try {
+ beginInput();
+ monitor.beginTask(null, 100);
+ removeSyncSetChangedListener(listener);
+ addSyncSetChangedListener(listener);
+ listener.syncInfoSetReset(SubscriberSyncInfoSet.this, Policy.subMonitorFor(monitor, 95));
+ } finally {
+ endInput(Policy.subMonitorFor(monitor, 5));
+ monitor.done();
+ }
+ }
+ }, true /* high priority */);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoStatistics.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoStatistics.java
new file mode 100644
index 000000000..7372b400f
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoStatistics.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.team.core.synchronize.SyncInfo;
+
+/**
+ * Counts SyncInfo states and allows for easy querying for different sync states.
+ */
+public class SyncInfoStatistics {
+ // {int sync kind -> int number of infos with that sync kind in this sync set}
+ protected Map stats = new HashMap();
+
+ /**
+ * Count this sync kind. Only the type of the sync info is stored.
+ * @param info the new info
+ */
+ public void add(SyncInfo info) {
+ // update statistics
+ Long count = (Long)stats.get(new Integer(info.getKind()));
+ if(count == null) {
+ count = new Long(0);
+ }
+ stats.put(new Integer(info.getKind()), new Long(count.longValue() + 1));
+ }
+
+ /**
+ * Remove this sync kind.
+ * @param info the info type to remove
+ */
+ public void remove(SyncInfo info) {
+ // update stats
+ Integer kind = new Integer(info.getKind());
+ Long count = (Long)stats.get(kind);
+ if(count == null) {
+ // error condition, shouldn't be removing if we haven't added yet
+ // programmer error calling remove before add.
+ } else {
+ long newCount = count.intValue() - 1;
+ if(newCount > 0) {
+ stats.put(kind, new Long(newCount));
+ } else {
+ stats.remove(kind);
+ }
+ }
+ }
+
+ /**
+ * Return the count of sync infos for the specified sync kind. A mask can be used to acucmulate
+ * counts for specific directions or change types.
+ * To return the number of outgoing changes:
+ * long outgoingChanges = stats.countFor(SyncInfo.OUTGOING, SyncInfo.DIRECTION_MASK);
+ *
+ * @param kind the sync kind for which to return the count
+ * @param mask the mask applied to the stored sync kind
+ * @return the number of sync info types added for the specific kind
+ */
+ public long countFor(int kind, int mask) {
+ if(mask == 0) {
+ Long count = (Long)stats.get(new Integer(kind));
+ return count == null ? 0 : count.longValue();
+ } else {
+ Iterator it = stats.keySet().iterator();
+ long count = 0;
+ while (it.hasNext()) {
+ Integer key = (Integer) it.next();
+ if((key.intValue() & mask) == kind) {
+ count += ((Long)stats.get(key)).intValue();
+ }
+ }
+ return count;
+ }
+ }
+
+ /**
+ * Clear the statistics counts. All calls to countFor() will return 0 until new
+ * sync infos are added.
+ */
+ public void clear() {
+ stats.clear();
+ }
+
+ /**
+ * For debugging
+ */
+ public String toString() {
+ StringBuffer out = new StringBuffer();
+ Iterator it = stats.keySet().iterator();
+ while (it.hasNext()) {
+ Integer kind = (Integer) it.next();
+ out.append(SyncInfo.kindToString(kind.intValue()) + ": " + ((Long)stats.get(kind)) + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return out.toString();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoTreeChangeEvent.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoTreeChangeEvent.java
new file mode 100644
index 000000000..5cb06dd60
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoTreeChangeEvent.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers;
+
+import java.util.*;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.synchronize.ISyncInfoTreeChangeEvent;
+import org.eclipse.team.core.synchronize.SyncInfoSet;
+
+public class SyncInfoTreeChangeEvent extends SyncSetChangedEvent implements ISyncInfoTreeChangeEvent {
+
+ private Set removedSubtrees = new HashSet();
+ private Set addedSubtrees = new HashSet();
+
+ public SyncInfoTreeChangeEvent(SyncInfoSet set) {
+ super(set);
+ }
+
+ public void removedSubtreeRoot(IResource root) {
+ if (addedSubtrees.contains(root)) {
+ // The root was added and removed which is a no-op
+ addedSubtrees.remove(root);
+ } else if (isDescendantOfAddedRoot(root)) {
+ // Nothing needs to be done since no listeners ever knew about the root
+ } else {
+ // check if the root is a child of an existing root
+ // (in which case it need not be added).
+ // Also, remove any exisiting roots that are children
+ // of the new root
+ for (Iterator iter = removedSubtrees.iterator(); iter.hasNext();) {
+ IResource element = (IResource) iter.next();
+ // check if the root is already in the list
+ if (root.equals(element)) return;
+ if (isParent(root, element)) {
+ // the root invalidates the current element
+ iter.remove();
+ } else if (isParent(element, root)) {
+ // the root is a child of an existing element
+ return;
+ }
+ }
+ removedSubtrees.add(root);
+ }
+ }
+
+ private boolean isParent(IResource root, IResource element) {
+ return root.getFullPath().isPrefixOf(element.getFullPath());
+ }
+
+ public void addedSubtreeRoot(IResource parent) {
+ if (removedSubtrees.contains(parent)) {
+ // The root was re-added. Just removing the removedRoot
+ // may not give the proper event.
+ // Since we can't be sure, just force a reset.
+ reset();
+ } else {
+ // only add the root if their isn't a higher root in the list already
+ if (!isDescendantOfAddedRoot(parent)) {
+ addedSubtrees.add(parent);
+ }
+ }
+ }
+
+ private boolean isDescendantOfAddedRoot(IResource resource) {
+ for (Iterator iter = addedSubtrees.iterator(); iter.hasNext();) {
+ IResource root = (IResource) iter.next();
+ if (isParent(root, resource)) {
+ // There is a higher added root already in the list
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public IResource[] getAddedSubtreeRoots() {
+ return (IResource[]) addedSubtrees.toArray(new IResource[addedSubtrees.size()]);
+ }
+
+ public IResource[] getRemovedSubtreeRoots() {
+ return (IResource[]) removedSubtrees.toArray(new IResource[removedSubtrees.size()]);
+ }
+
+ public boolean isEmpty() {
+ return super.isEmpty() && removedSubtrees.isEmpty() && addedSubtrees.isEmpty();
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoWorkingSetFilter.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoWorkingSetFilter.java
new file mode 100644
index 000000000..ca632a6b0
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoWorkingSetFilter.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers;
+
+import java.util.*;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.*;
+import org.eclipse.team.core.synchronize.*;
+import org.eclipse.team.internal.core.TeamPlugin;
+
+/**
+ * WorkingSet filter for a SyncSet.
+ */
+public class SyncInfoWorkingSetFilter extends FastSyncInfoFilter {
+
+ private IResource[] resources;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ui.sync.SyncInfoFilter#select(org.eclipse.team.core.subscribers.SyncInfo)
+ */
+ public boolean select(SyncInfo info) {
+ // if there's no set, the resource is included
+ if (isEmpty()) return true;
+ return isIncluded(info.getLocal());
+ }
+
+ /*
+ * Answer true if the given resource is included in the working set
+ */
+ private boolean isIncluded(IResource resource) {
+ // otherwise, if their is a parent of the resource in the set,
+ // it is included
+ List result = new ArrayList();
+ for (int i = 0; i < resources.length; i++) {
+ IResource setResource = resources[i];
+ if (isParent(setResource, resource)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isParent(IResource parent, IResource child) {
+ return (parent.getFullPath().isPrefixOf(child.getFullPath()));
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.views.SyncSetInputFromSubscriber#getRoots()
+ */
+ public IResource[] getRoots(Subscriber subscriber) {
+ IResource[] roots = subscriber.roots();
+ if (isEmpty()) return roots;
+
+ // filter the roots by the selected working set
+ Set result = new HashSet();
+ for (int i = 0; i < roots.length; i++) {
+ IResource resource = roots[i];
+ result.addAll(Arrays.asList(getIntersectionWithSet(subscriber, resource)));
+ }
+ return (IResource[]) result.toArray(new IResource[result.size()]);
+ }
+
+ /*
+ * Answer the intersection between the given resource and it's children
+ * and the receiver's working set.
+ */
+ private IResource[] getIntersectionWithSet(Subscriber subscriber, IResource resource) {
+ List result = new ArrayList();
+ for (int i = 0; i < resources.length; i++) {
+ IResource setResource = resources[i];
+ if (setResource != null) {
+ if (isParent(resource, setResource)) {
+ try {
+ if (subscriber.isSupervised(setResource)) {
+ result.add(setResource);
+ }
+ } catch (TeamException e) {
+ // Log the exception and add the resource to the list
+ TeamPlugin.log(e);
+ result.add(setResource);
+ }
+ } else if (isParent(setResource, resource)) {
+ result.add(resource);
+ }
+ }
+ }
+ return (IResource[]) result.toArray(new IResource[result.size()]);
+ }
+
+ public void setWorkingSet(IResource[] resources) {
+ this.resources = resources;
+ }
+
+ public IResource[] getWorkingSet() {
+ return this.resources;
+ }
+
+ private boolean isEmpty() {
+ return resources == null || resources.length == 0;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetChangedEvent.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetChangedEvent.java
new file mode 100644
index 000000000..1581ba012
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetChangedEvent.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers;
+
+import java.util.*;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.ITeamStatus;
+import org.eclipse.team.core.synchronize.*;
+
+/**
+ * This event keeps track of the changes in a sync set
+ */
+public class SyncSetChangedEvent implements ISyncInfoSetChangeEvent {
+
+ private SyncInfoSet set;
+
+ // List that accumulate changes
+ // SyncInfo
+ private Set changedResources = new HashSet();
+ private Set removedResources = new HashSet();
+ private Set addedResources = new HashSet();
+
+ private boolean reset = false;
+
+ private boolean errorAdded;
+
+ private List errors = new ArrayList();
+
+ public SyncSetChangedEvent(SyncInfoSet set) {
+ super();
+ this.set = set;
+ }
+
+ public void added(SyncInfo info) {
+ if (removedResources.contains(info.getLocal())) {
+ // A removal followed by an addition is treated as a change
+ removedResources.remove(info.getLocal());
+ changed(info);
+ } else {
+ addedResources.add(info);
+ }
+ }
+
+ public void removed(IResource resource, SyncInfo info) {
+ if (changedResources.contains(info)) {
+ // No use in reporting the change since it has subsequently been removed
+ changedResources.remove(info);
+ } else if (addedResources.contains(info)) {
+ // An addition followed by a removal can be dropped
+ addedResources.remove(info);
+ return;
+ }
+ removedResources.add(resource);
+ }
+
+ public void changed(SyncInfo info) {
+ changedResources.add(info);
+ }
+
+ public SyncInfo[] getAddedResources() {
+ return (SyncInfo[]) addedResources.toArray(new SyncInfo[addedResources.size()]);
+ }
+
+ public SyncInfo[] getChangedResources() {
+ return (SyncInfo[]) changedResources.toArray(new SyncInfo[changedResources.size()]);
+ }
+
+ public IResource[] getRemovedResources() {
+ return (IResource[]) removedResources.toArray(new IResource[removedResources.size()]);
+ }
+
+ public SyncInfoSet getSet() {
+ return set;
+ }
+
+ public void reset() {
+ reset = true;
+ }
+
+ public boolean isReset() {
+ return reset;
+ }
+
+ public boolean isEmpty() {
+ return changedResources.isEmpty() && removedResources.isEmpty() && addedResources.isEmpty() && errors.isEmpty();
+ }
+
+ public void errorOccurred(ITeamStatus status) {
+ errors.add(status);
+ }
+
+ public ITeamStatus[] getErrors() {
+ return (ITeamStatus[]) errors.toArray(new ITeamStatus[errors.size()]);
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInput.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInput.java
new file mode 100644
index 000000000..3935cffe8
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInput.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.*;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * This is the superclass for all SyncSet input providers
+ */
+public abstract class SyncSetInput {
+
+ private SubscriberSyncInfoSet syncSet;
+ private SyncInfoFilter filter = new FastSyncInfoFilter();
+
+ public SyncSetInput(SubscriberEventHandler handler) {
+ syncSet = new SubscriberSyncInfoSet(handler);
+ }
+
+ public SubscriberSyncInfoSet getSyncSet() {
+ return syncSet;
+ }
+
+ /**
+ * This method is invoked from reset to get all the sync information from
+ * the input source.
+ */
+ protected abstract void fetchInput(IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * The input is no longer being used. Disconnect it from its source.
+ */
+ public abstract void disconnect();
+
+ /**
+ * Reset the input. This will clear the current contents of the sync set and
+ * obtain the contents from the input source.
+ */
+ public void reset(IProgressMonitor monitor) throws TeamException {
+ syncSet.beginInput();
+ try {
+ monitor = Policy.monitorFor(monitor);
+ monitor.beginTask(null, 100);
+ syncSet.clear();
+ fetchInput(Policy.subMonitorFor(monitor, 90));
+ } finally {
+ syncSet.endInput(Policy.subMonitorFor(monitor, 10));
+ monitor.done();
+ }
+ }
+
+ /**
+ * Collect the change in the provided sync info.
+ */
+ protected void collect(SyncInfo info, IProgressMonitor monitor) {
+ boolean isOutOfSync = filter.select(info, monitor);
+ SyncInfo oldInfo = syncSet.getSyncInfo(info.getLocal());
+ boolean wasOutOfSync = oldInfo != null;
+ if (isOutOfSync) {
+ syncSet.add(info);
+ } else if (wasOutOfSync) {
+ syncSet.remove(info.getLocal());
+ }
+ }
+
+ protected void remove(IResource resource) {
+ SyncInfo oldInfo = syncSet.getSyncInfo(resource);
+ boolean wasOutOfSync = oldInfo != null;
+ if (oldInfo != null) {
+ syncSet.remove(resource);
+ }
+ }
+
+ public SyncInfoFilter getFilter() {
+ return filter;
+ }
+
+ public void setFilter(SyncInfoFilter filter) {
+ this.filter = filter;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSubscriber.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSubscriber.java
new file mode 100644
index 000000000..19f03d931
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSubscriber.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.ITeamStatus;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.Subscriber;
+
+/**
+ * Records resource synchronization changes from a Team subscriber. The actual changes
+ * are calculated via the SubscriberEventHandler and stored in this input.
+ */
+public class SyncSetInputFromSubscriber extends SyncSetInput {
+
+ private Subscriber subscriber;
+
+ public SyncSetInputFromSubscriber(Subscriber subscriber, SubscriberEventHandler handler) {
+ super(handler);
+ this.subscriber = subscriber;
+ }
+
+ public void disconnect() {
+ }
+
+ public Subscriber getSubscriber() {
+ return subscriber;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.views.SyncSetInput#fetchInput(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void fetchInput(IProgressMonitor monitor) throws TeamException {
+ // don't calculate changes. The SubscriberEventHandler will fetch the
+ // input in a job and update this sync set when the changes are
+ // calculated.
+ }
+
+ /**
+ * Handle an error that occurred while populating the receiver's set.
+ * The <code>ITeamStatus</code> includes the resource for which the
+ * error occurred.
+ * This error is propogated to any set listeners.
+ * @param status the error status
+ */
+ public void handleError(ITeamStatus status) {
+ getSyncSet().addError(status);
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSyncSet.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSyncSet.java
new file mode 100644
index 000000000..3d84b4532
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSyncSet.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.ITeamStatus;
+import org.eclipse.team.core.synchronize.*;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * Ths class uses the contents of one sync set as the input of another.
+ */
+public class SyncSetInputFromSyncSet extends SyncSetInput implements ISyncInfoSetChangeListener {
+
+ SubscriberSyncInfoSet inputSyncSet;
+
+ public SyncSetInputFromSyncSet(SubscriberSyncInfoSet set, SubscriberEventHandler handler) {
+ super(handler);
+ this.inputSyncSet = set;
+ inputSyncSet.addSyncSetChangedListener(this);
+ }
+
+ public SyncInfoSet getInputSyncSet() {
+ return inputSyncSet;
+ }
+
+ public void disconnect() {
+ if (inputSyncSet == null) return;
+ inputSyncSet.removeSyncSetChangedListener(this);
+ inputSyncSet = null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.AbstractSyncSet#initialize(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void fetchInput(IProgressMonitor monitor) {
+ if (inputSyncSet == null) return;
+ SyncInfo[] infos = inputSyncSet.getSyncInfos();
+ for (int i = 0; i < infos.length; i++) {
+ collect(infos[i], monitor);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.ISyncSetChangedListener#syncSetChanged(org.eclipse.team.ccvs.syncviews.views.SyncSetChangedEvent)
+ */
+ public void syncInfoChanged(ISyncInfoSetChangeEvent event, IProgressMonitor monitor) {
+ SyncInfoSet syncSet = getSyncSet();
+ try {
+ syncSet.beginInput();
+ monitor.beginTask(null, 105);
+ syncSetChanged(event.getChangedResources(), Policy.subMonitorFor(monitor, 50));
+ syncSetChanged(event.getAddedResources(), Policy.subMonitorFor(monitor, 50));
+ remove(event.getRemovedResources());
+ } finally {
+ syncSet.endInput(Policy.subMonitorFor(monitor, 5));
+ }
+ }
+
+ private void syncSetChanged(SyncInfo[] infos, IProgressMonitor monitor) {
+ for (int i = 0; i < infos.length; i++) {
+ collect(infos[i], monitor);
+ }
+ }
+
+ private void remove(IResource[] resources) {
+ for (int i = 0; i < resources.length; i++) {
+ remove(resources[i]);
+ }
+ }
+
+ public void reset() {
+ inputSyncSet.connect(this);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.subscribers.ISyncInfoSetChangeListener#syncInfoSetReset(org.eclipse.team.core.subscribers.SyncInfoSet, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void syncInfoSetReset(SyncInfoSet set, IProgressMonitor monitor) {
+ SyncInfoSet syncSet = getSyncSet();
+ try {
+ syncSet.beginInput();
+ monitor.beginTask(null, 100);
+ syncSet.clear();
+ fetchInput(Policy.subMonitorFor(monitor, 95));
+ } finally {
+ syncSet.endInput(Policy.subMonitorFor(monitor, 5));
+ monitor.done();
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.subscribers.ISyncInfoSetChangeListener#syncInfoSetError(org.eclipse.team.core.subscribers.SyncInfoSet, org.eclipse.team.core.ITeamStatus[], org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void syncInfoSetErrors(SyncInfoSet set, ITeamStatus[] errors, IProgressMonitor monitor) {
+ SubscriberSyncInfoSet syncSet = getSyncSet();
+ try {
+ syncSet.beginInput();
+ for (int i = 0; i < errors.length; i++) {
+ ITeamStatus status = errors[i];
+ syncSet.addError(status);
+ }
+ } finally {
+ syncSet.endInput(monitor);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/WorkingSetSyncSetInput.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/WorkingSetSyncSetInput.java
new file mode 100644
index 000000000..7cb0fdbcb
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/WorkingSetSyncSetInput.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.subscribers.Subscriber;
+
+public class WorkingSetSyncSetInput extends SyncSetInputFromSyncSet {
+
+ private SyncInfoWorkingSetFilter workingSetFilter = new SyncInfoWorkingSetFilter();
+
+ public WorkingSetSyncSetInput(SubscriberSyncInfoSet set, SubscriberEventHandler handler) {
+ super(set, handler);
+ setFilter(workingSetFilter);
+ }
+
+ public void setWorkingSet(IResource[] resources) {
+ workingSetFilter.setWorkingSet(resources);
+ }
+
+ public IResource[] getWorkingSet() {
+ return workingSetFilter.getWorkingSet();
+ }
+
+ public IResource[] roots(Subscriber subscriber) {
+ return workingSetFilter.getRoots(subscriber);
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/DescendantResourceVariantTree.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/DescendantResourceVariantTree.java
new file mode 100644
index 000000000..e3653f968
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/DescendantResourceVariantTree.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers.caches;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.TeamException;
+
+/**
+ * A <code>ResourceVariantTree</code> that optimizes the memory footprint
+ * of a remote resource variant tree by only storing those bytes that
+ * differ from a base resource variant tree. This class should only be used
+ * for cases where the base and remote are on the same line-of-descent.
+ * For example, when the remote tree represents the current state of a branch
+ * and the base represents the state of the same branch when the local workspace
+ * as last refreshed.
+ * <p>
+ * This class also contains the logic that allows subclasses to determine if
+ * bytes stored in the remote tree are on a different line-of-descent than the base.
+ * This is necessary because it is possible for the base tree to change in ways that
+ * invalidate the stored remote variants. For example, if the local resources are moved
+ * from the main trunck to a branch, any cached remote resource variants would be stale.
+
+ */
+public abstract class DescendantResourceVariantTree extends ResourceVariantTree {
+ ResourceVariantTree baseCache, remoteCache;
+
+ public DescendantResourceVariantTree(ResourceVariantTree baseCache, ResourceVariantTree remoteCache) {
+ this.baseCache = baseCache;
+ this.remoteCache = remoteCache;
+ }
+
+ /**
+ * This method will dispose the remote cache but not the base cache.
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#dispose()
+ */
+ public void dispose() {
+ remoteCache.dispose();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#getBytes(org.eclipse.core.resources.IResource)
+ */
+ public byte[] getBytes(IResource resource) throws TeamException {
+ byte[] remoteBytes = remoteCache.getBytes(resource);
+ byte[] baseBytes = baseCache.getBytes(resource);
+ if (baseBytes == null) {
+ // There is no base so use the remote bytes
+ return remoteBytes;
+ }
+ if (remoteBytes == null) {
+ if (isVariantKnown(resource)) {
+ // The remote is known to not exist
+ // TODO: The check for NO_REMOTE does not take into consideration the line-of-descent
+ return remoteBytes;
+ } else {
+ // The remote was either never queried or was the same as the base.
+ // In either of these cases, the base bytes are used.
+ return baseBytes;
+ }
+ }
+ if (isDescendant(resource, baseBytes, remoteBytes)) {
+ // Only use the remote bytes if they are later on the same line-of-descent as the base
+ return remoteBytes;
+ }
+ // Use the base sbytes since the remote bytes must be stale (i.e. are
+ // not on the same line-of-descent
+ return baseBytes;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#setBytes(org.eclipse.core.resources.IResource, byte[])
+ */
+ public boolean setBytes(IResource resource, byte[] bytes) throws TeamException {
+ byte[] baseBytes = baseCache.getBytes(resource);
+ if (baseBytes != null && equals(baseBytes, bytes)) {
+ // Remove the existing bytes so the base will be used (thus saving space)
+ return remoteCache.removeBytes(resource, IResource.DEPTH_ZERO);
+ } else {
+ return remoteCache.setBytes(resource, bytes);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#removeBytes(org.eclipse.core.resources.IResource, int)
+ */
+ public boolean removeBytes(IResource resource, int depth) throws TeamException {
+ return remoteCache.removeBytes(resource, depth);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#isVariantKnown(org.eclipse.core.resources.IResource)
+ */
+ public boolean isVariantKnown(IResource resource) throws TeamException {
+ return remoteCache.isVariantKnown(resource);
+ }
+
+ /**
+ * This method indicates whether the remote bytes are a later revision or version
+ * on the same line-of-descent as the base. A line of descent may be a branch or a fork
+ * (depending on the terminology used by the versioing server). If this method returns
+ * <code>false</code> then the remote bytes will be ignored by this tree.
+ * @param resource the local resource
+ * @param baseBytes the base bytes for the local resoource
+ * @param remoteBytes the remote bytes for the local resoource
+ * @return whether the remote bytes are later on the same line-of-descent as the base bytes
+ */
+ protected abstract boolean isDescendant(IResource resource, byte[] baseBytes, byte[] remoteBytes) throws TeamException;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#setVariantDoesNotExist(org.eclipse.core.resources.IResource)
+ */
+ public boolean setVariantDoesNotExist(IResource resource) throws TeamException {
+ return remoteCache.setVariantDoesNotExist(resource);
+ }
+
+ /**
+ * Return the base tree from which the remote is descendant.
+ * @return Returns the base tree.
+ */
+ protected ResourceVariantTree getBaseTree() {
+ return baseCache;
+ }
+
+ /**
+ * Return the remote tree which contains bytes only for the resource variants
+ * that differ from those in the base tree.
+ * @return Returns the remote tree.
+ */
+ protected ResourceVariantTree getRemoteTree() {
+ return remoteCache;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#members(org.eclipse.core.resources.IResource)
+ */
+ public IResource[] members(IResource resource) throws TeamException {
+ return getRemoteTree().members(resource);
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/PersistantResourceVariantTree.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/PersistantResourceVariantTree.java
new file mode 100644
index 000000000..d998c9cd7
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/PersistantResourceVariantTree.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers.caches;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.internal.core.Assert;
+
+/**
+ * A <code>ResourceVariantTree</code> that caches the variant bytes using
+ * the <code>org.eclipse.core.resources.ISynchronizer</code> so that
+ * the tree is cached accross workbench invocations.
+ */
+public class PersistantResourceVariantTree extends ResourceVariantTree {
+
+ private static final byte[] NO_REMOTE = new byte[0];
+
+ private QualifiedName syncName;
+
+ /**
+ * Create a persistant tree that uses the given qualified name
+ * as the key in the <code>org.eclipse.core.resources.ISynchronizer</code>.
+ * It must be unique and should use the plugin as the local name
+ * and a unique id within the plugin as the qualifier name.
+ * @param name the key used in the Core synchronizer
+ */
+ public PersistantResourceVariantTree(QualifiedName name) {
+ syncName = name;
+ getSynchronizer().add(syncName);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#dispose()
+ */
+ public void dispose() {
+ getSynchronizer().remove(getSyncName());
+ }
+
+ /**
+ * Convenience method that returns the Core <code>ISynchronizer</code>.
+ */
+ protected ISynchronizer getSynchronizer() {
+ return ResourcesPlugin.getWorkspace().getSynchronizer();
+ }
+
+ /**
+ * Return the qualified name that uniquely identifies this tree.
+ * @return the qwualified name that uniquely identifies this tree.
+ */
+ public QualifiedName getSyncName() {
+ return syncName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#getBytes(org.eclipse.core.resources.IResource)
+ */
+ public byte[] getBytes(IResource resource) throws TeamException {
+ byte[] syncBytes = internalGetSyncBytes(resource);
+ if (syncBytes != null && equals(syncBytes, NO_REMOTE)) {
+ // If it is known that there is no remote, return null
+ return null;
+ }
+ return syncBytes;
+ }
+
+ private byte[] internalGetSyncBytes(IResource resource) throws TeamException {
+ try {
+ return getSynchronizer().getSyncInfo(getSyncName(), resource);
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#setBytes(org.eclipse.core.resources.IResource, byte[])
+ */
+ public boolean setBytes(IResource resource, byte[] bytes) throws TeamException {
+ Assert.isNotNull(bytes);
+ byte[] oldBytes = internalGetSyncBytes(resource);
+ if (oldBytes != null && equals(oldBytes, bytes)) return false;
+ try {
+ getSynchronizer().setSyncInfo(getSyncName(), resource, bytes);
+ return true;
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#removeBytes(org.eclipse.core.resources.IResource, int)
+ */
+ public boolean removeBytes(IResource resource, int depth) throws TeamException {
+ if (resource.exists() || resource.isPhantom()) {
+ try {
+ if (depth != IResource.DEPTH_ZERO || internalGetSyncBytes(resource) != null) {
+ getSynchronizer().flushSyncInfo(getSyncName(), resource, depth);
+ return true;
+ }
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#isVariantKnown(org.eclipse.core.resources.IResource)
+ */
+ public boolean isVariantKnown(IResource resource) throws TeamException {
+ return internalGetSyncBytes(resource) != null;
+ }
+
+ /**
+ * This method should be invoked by a client to indicate that it is known that
+ * there is no remote resource associated with the local resource. After this method
+ * is invoked, <code>isRemoteKnown(resource)</code> will return <code>true</code> and
+ * <code>getSyncBytes(resource)</code> will return <code>null</code>.
+ * @return <code>true</code> if this changes the remote sync bytes
+ */
+ public boolean setVariantDoesNotExist(IResource resource) throws TeamException {
+ return setBytes(resource, NO_REMOTE);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#members(org.eclipse.core.resources.IResource)
+ */
+ public IResource[] members(IResource resource) throws TeamException {
+ if(resource.getType() == IResource.FILE) {
+ return new IResource[0];
+ }
+ try {
+ // Filter and return only resources that have sync bytes in the cache.
+ IResource[] members = ((IContainer)resource).members(true /* include phantoms */);
+ List filteredMembers = new ArrayList(members.length);
+ for (int i = 0; i < members.length; i++) {
+ IResource member = members[i];
+ if(getBytes(member) != null) {
+ filteredMembers.add(member);
+ }
+ }
+ return (IResource[]) filteredMembers.toArray(new IResource[filteredMembers.size()]);
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTree.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTree.java
new file mode 100644
index 000000000..36404a5c5
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTree.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers.caches;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.TeamException;
+
+/**
+ * The purpose of a <code>ResourceVariantTree</code> is to support the caching of
+ * the synchronization bytes for the resource variants that represent
+ * a resource line-up of interest such as a version, baseline or branch. The
+ * cache stores bytes in order to minimize the memory footprint of the tree. It is the
+ * reponsibility of the client of this API to cache enough bytes to meaningfully identify
+ * a resource variant (and possibly create an {@link IResourceVariant} handle from them).
+ * <p>
+ * The bytes for
+ * a resource variant are accessed using the local handle that corresponds to the
+ * resource variant (using the <code>getSyncInfo</code> method).
+ * The potential children of a resource variant are also accessed
+ * by using the local handle that corresponds to the resource variant
+ * (using the <code>members</code> method).
+ * TODO: Does the isRemoteKnown/setRemoteDoesNotExist make sense?
+ */
+public abstract class ResourceVariantTree {
+
+ /**
+ * Dispose of any cached sync bytes when this cache is no longer needed.
+ */
+ public abstract void dispose();
+
+ /**
+ * Return the bytes for the variant corresponding the given local resource.
+ * A return value of <code>null</code> can mean either that the
+ * variant has never been fetched or that it doesn't exist. The method
+ * <code>isVariantKnown(IResource)</code> should be used to differentiate
+ * these two cases.
+ * @param resource the local resource
+ * @return the bytes that represent the resource's variant
+ * @throws TeamException
+ */
+ public abstract byte[] getBytes(IResource resource) throws TeamException;
+
+ /**
+ * Set the bytes for the variant corresponding the given local resource.
+ * The bytes should never be
+ * <code>null</code>. If it is known that the remote does not exist,
+ * <code>setVariantDoesNotExist(IResource)</code> should be used instead. If the sync
+ * bytes for the remote are stale and should be removed, <code>removeBytes()</code>
+ * should be called.
+ * @param resource the local resource
+ * @param bytes the bytes that represent the resource's variant
+ * @return <code>true</code> if the bytes changed
+ * @throws TeamException
+ */
+ public abstract boolean setBytes(IResource resource, byte[] bytes) throws TeamException;
+
+ /**
+ * Remove the bytes from the tree for the resource variants corresponding to the
+ * local resources that are descendants of the giben locla resource to the given depth.
+ * After the bytes are removed, the operation <code>isVariantKnown(resource)</code>
+ * will return <code>false</code>
+ * and <code>getBytes(resource)</code> will return <code>null</code> for the
+ * affected resources.
+ * @param resource the local resource
+ * @parem depth the depth of the operation (one of <code>IResource.DEPTH_ZERO</code>,
+ * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
+ * @return <code>true</code> if there were bytes present which were removed
+ * @throws TeamException
+ */
+ public abstract boolean removeBytes(IResource resource, int depth) throws TeamException;
+
+ /**
+ * Return <code>true</code> if the variant associated with the given local
+ * resource has been cached. This method is useful for those cases when
+ * there are no bytes for a resource variant and the client wants to
+ * know if this means that the remote does exist (i.e. this method returns
+ * <code>true</code>) or the remote has not been fetched (i.e. this method returns
+ * <code>false</code>).
+ * @param resource the local resource
+ * @return <code>true</code> if the variant associated with the given local
+ * resource has been cached.
+ * @throws TeamException
+ */
+ public abstract boolean isVariantKnown(IResource resource) throws TeamException;
+
+ /**
+ * This method should be invoked by a client to indicate that it is known that
+ * there is no variant associated with the local resource. After this method
+ * is invoked, <code>isVariantKnown(resource)</code> will return <code>true</code> and
+ * <code>getBytes(resource)</code> will return <code>null</code>.
+ * @param resource the local resource
+ * @return <code>true</code> if this changes the bytes for the variant
+ */
+ public abstract boolean setVariantDoesNotExist(IResource resource) throws TeamException;
+
+ /**
+ * Return the children of the given resource that have resource variants in this tree.
+ * @param resource the parent resource
+ * @return the members who have resource variants in this tree.
+ */
+ public abstract IResource[] members(IResource resource) throws TeamException;
+
+ /**
+ * Helper method to compare two byte arrays for equality
+ * @param syncBytes1 the first byte array or <code>null</code>
+ * @param syncBytes2 the second byte array or <code>null</code>
+ * @return whetehr the two arrays are equal (i.e. same content)
+ */
+ protected boolean equals(byte[] syncBytes1, byte[] syncBytes2) {
+ if (syncBytes1 == null) {
+ return syncBytes2 == null;
+ } else if (syncBytes2 == null) {
+ return false;
+ }
+ if (syncBytes1.length != syncBytes2.length) return false;
+ for (int i = 0; i < syncBytes1.length; i++) {
+ if (syncBytes1[i] != syncBytes2[i]) return false;
+ }
+ return true;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTreeRefresh.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTreeRefresh.java
new file mode 100644
index 000000000..1c7395c7e
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTreeRefresh.java
@@ -0,0 +1,339 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers.caches;
+
+import java.util.*;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.IResourceVariant;
+import org.eclipse.team.internal.core.Assert;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * This class provides the logic for refreshing a resource variant tree.
+ * It provides the logic to traverse the local resource and variant resource trees in
+ * order to update the bytes stored in
+ * a <code>ResourceVariantTree</code>. It also accumulates and returns all local resources
+ * for which the corresponding resource variant has changed.
+ */
+public abstract class ResourceVariantTreeRefresh {
+
+ /**
+ * Refreshes the resource variant tree for the specified resources and possibly their descendants,
+ * depending on the depth. The default implementation of this method invokes
+ * <code>refresh(IResource, int, IProgressMonitor)</code> for each resource.
+ * Subclasses may override but should either invoke the above mentioned refresh or
+ * <code>collectChanges</code> in order to reconcile the resource variant tree.
+ * @param resources the resources whose variants should be refreshed
+ * @param depth the depth of the refresh (one of <code>IResource.DEPTH_ZERO</code>,
+ * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
+ * @param monitor a progress monitor
+ * @return the array of resources whose corresponding varianst have changed
+ * @throws TeamException
+ */
+ public IResource[] refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException {
+ List changedResources = new ArrayList();
+ monitor.beginTask(null, 100 * resources.length);
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ IResource[] changed = refresh(resource, depth, Policy.subMonitorFor(monitor, 100));
+ changedResources.addAll(Arrays.asList(changed));
+ }
+ monitor.done();
+ if (changedResources == null) return new IResource[0];
+ return (IResource[]) changedResources.toArray(new IResource[changedResources.size()]);
+ }
+
+ /**
+ * Helper method invoked from <code>refresh(IResource[], int, IProgressMonitor monitor)</code>
+ * for each resource. The default implementation performs the following steps:
+ * <ol>
+ * <li>obtaine the scheduling rule for the resource
+ * as returned from <code>getSchedulingRule(IResource)</code>.
+ * <li>get the resource variant handle corresponding to the local resource by calling
+ * <code>getRemoteTree</code>.
+ * <li>pass the local resource and the resource variant handle to <code>collectChanges</code>
+ * </ol>
+ * Subclasses may override but should perform roughly the same steps.
+ * @param resource the resoure being refreshed
+ * @param depth the depth of the refresh (one of <code>IResource.DEPTH_ZERO</code>,
+ * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
+ * @param monitor a progress monitor
+ * @return the resource's whose variants have changed
+ * @throws TeamException
+ */
+ protected IResource[] refresh(IResource resource, int depth, IProgressMonitor monitor) throws TeamException {
+ IResource[] changedResources = null;
+ monitor.beginTask(null, 100);
+ ISchedulingRule rule = getSchedulingRule(resource);
+ try {
+ Platform.getJobManager().beginRule(rule, monitor);
+ if (!resource.getProject().isAccessible()) {
+ // The project is closed so silently skip it
+ return new IResource[0];
+ }
+
+ monitor.setTaskName(Policy.bind("SynchronizationCacheRefreshOperation.0", resource.getFullPath().makeRelative().toString())); //$NON-NLS-1$
+
+ // build the remote tree only if an initial tree hasn't been provided
+ IResourceVariant tree = fetchVariant(resource, depth, Policy.subMonitorFor(monitor, 70));
+
+ // update the known remote handles
+ IProgressMonitor sub = Policy.infiniteSubMonitorFor(monitor, 30);
+ try {
+ sub.beginTask(null, 64);
+ changedResources = collectChanges(resource, tree, depth, sub);
+ } finally {
+ sub.done();
+ }
+ } finally {
+ Platform.getJobManager().endRule(rule);
+ monitor.done();
+ }
+ if (changedResources == null) return new IResource[0];
+ return changedResources;
+ }
+
+ /**
+ * Collect the changes in the remote tree to the specified depth.
+ * @param local the local resource being refreshed
+ * @param remote the corresponding resource variant
+ * @param depth the depth of the refresh (one of <code>IResource.DEPTH_ZERO</code>,
+ * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
+ * @param monitor a progress monitor
+ * @return the resource's whose variants have changed
+ * @throws TeamException
+ */
+ protected IResource[] collectChanges(IResource local, IResourceVariant remote, int depth, IProgressMonitor monitor) throws TeamException {
+ List changedResources = new ArrayList();
+ collectChanges(local, remote, changedResources, depth, monitor);
+ return (IResource[]) changedResources.toArray(new IResource[changedResources.size()]);
+ }
+
+ /**
+ * Returns the resource variant tree that is being refreshed.
+ * @return the resource variant tree that is being refreshed.
+ */
+ protected abstract ResourceVariantTree getResourceVariantTree();
+
+ /**
+ * Get the bytes to be stored in the <code>ResourceVariantTree</code>
+ * from the given resource variant.
+ * @param local the local resource
+ * @param remote the corresponding resource variant handle
+ * @return the bytes for the resource variant.
+ */
+ protected abstract byte[] getBytes(IResource local, IResourceVariant remote) throws TeamException;
+
+ /**
+ * Fetch the members of the given resource variant handle. This method may
+ * return members that were fetched when <code>getRemoteTree</code> was called or
+ * may fetch the children directly.
+ * @param variant the resource variant
+ * @param progress a progress monitor
+ * @return the members of the resource variant.
+ */
+ protected abstract IResourceVariant[] fetchMembers(IResourceVariant variant, IProgressMonitor progress) throws TeamException;
+
+ /**
+ * Returns the members of the local resource. This may include all the members of
+ * the local resource or a subset that is of ineterest to the implementor.
+ * @param parent the local resource
+ * @return the members of the local resource
+ */
+ protected abstract IResource[] members(IResource parent) throws TeamException;
+
+ /**
+ * Fetch the resource variant corresponding to the given resource.
+ * The depth
+ * parameter indicates the depth of the refresh operation and also indicates the
+ * depth to which the resource variant's desendants will be traversed.
+ * This method may prefetch the descendants to the provided depth
+ * or may just return the variant handle corresponding to the given
+ * local resource, in which case
+ * the descendant variants will be fetched by <code>fecthMembers(IResourceVariant, IProgressMonitor)</code>.
+ * @param resource the local resource
+ * @param depth the depth of the refresh (one of <code>IResource.DEPTH_ZERO</code>,
+ * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
+ * @param monitor a progress monitor
+ * @return the resource variant corresponding to the given local resource
+ */
+ protected abstract IResourceVariant fetchVariant(IResource resource, int depth, IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * Return the scheduling rule that should be obtained for the given resource.
+ * This method is invoked from <code>refresh(IResource, int, IProgressMonitor)</code>.
+ * By default, the resource's project is returned. Subclasses may override.
+ * @param resource the resource being refreshed
+ * @return a scheduling rule or <code>null</code>
+ */
+ protected ISchedulingRule getSchedulingRule(IResource resource) {
+ return resource.getProject();
+ }
+
+ private void collectChanges(IResource local, IResourceVariant remote, Collection changedResources, int depth, IProgressMonitor monitor) throws TeamException {
+ ResourceVariantTree cache = getResourceVariantTree();
+ byte[] newRemoteBytes = getBytes(local, remote);
+ boolean changed;
+ if (newRemoteBytes == null) {
+ changed = cache.setVariantDoesNotExist(local);
+ } else {
+ changed = cache.setBytes(local, newRemoteBytes);
+ }
+ if (changed) {
+ changedResources.add(local);
+ }
+ if (depth == IResource.DEPTH_ZERO) return;
+ Map children = mergedMembers(local, remote, monitor);
+ for (Iterator it = children.keySet().iterator(); it.hasNext();) {
+ IResource localChild = (IResource) it.next();
+ IResourceVariant remoteChild = (IResourceVariant)children.get(localChild);
+ collectChanges(localChild, remoteChild, changedResources,
+ depth == IResource.DEPTH_INFINITE ? IResource.DEPTH_INFINITE : IResource.DEPTH_ZERO,
+ monitor);
+ }
+
+ removeStaleBytes(local, children, changedResources);
+ }
+
+ private void removeStaleBytes(IResource local, Map children, Collection changedResources) throws TeamException {
+ // Look for resources that have sync bytes but are not in the resources we care about
+ ResourceVariantTree cache = getResourceVariantTree();
+ IResource[] resources = getChildrenWithBytes(local);
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ if (!children.containsKey(resource)) {
+ // These sync bytes are stale. Purge them
+ cache.removeBytes(resource, IResource.DEPTH_INFINITE);
+ changedResources.add(resource);
+ }
+ }
+ }
+
+ /*
+ * Return all the children of the local resource, including phantoms, that have bytes
+ * associated with them in the resource varant tree of this operation.
+ * @param local the local resource
+ * @return all children that have bytes stored in the tree.
+ * @throws TeamException
+ */
+ private IResource[] getChildrenWithBytes(IResource local) throws TeamException {
+ try {
+ if (local.getType() != IResource.FILE && (local.exists() || local.isPhantom())) {
+ IResource[] allChildren = ((IContainer)local).members(true /* include phantoms */);
+ List childrenWithSyncBytes = new ArrayList();
+ for (int i = 0; i < allChildren.length; i++) {
+ IResource resource = allChildren[i];
+ if (getResourceVariantTree().getBytes(resource) != null) {
+ childrenWithSyncBytes.add(resource);
+ }
+ }
+ return (IResource[]) childrenWithSyncBytes.toArray(
+ new IResource[childrenWithSyncBytes.size()]);
+ }
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ return new IResource[0];
+ }
+
+ private Map mergedMembers(IResource local, IResourceVariant remote, IProgressMonitor progress) throws TeamException {
+
+ // {IResource -> IRemoteResource}
+ Map mergedResources = new HashMap();
+
+ IResourceVariant[] remoteChildren;
+ if (remote == null) {
+ remoteChildren = new IResourceVariant[0];
+ } else {
+ remoteChildren = fetchMembers(remote, progress);
+ }
+
+
+ IResource[] localChildren = members(local);
+
+ if (remoteChildren.length > 0 || localChildren.length > 0) {
+ Set allSet = new HashSet(20);
+ Map localSet = null;
+ Map remoteSet = null;
+
+ if (localChildren.length > 0) {
+ localSet = new HashMap(10);
+ for (int i = 0; i < localChildren.length; i++) {
+ IResource localChild = localChildren[i];
+ String name = localChild.getName();
+ localSet.put(name, localChild);
+ allSet.add(name);
+ }
+ }
+
+ if (remoteChildren.length > 0) {
+ remoteSet = new HashMap(10);
+ for (int i = 0; i < remoteChildren.length; i++) {
+ IResourceVariant remoteChild = remoteChildren[i];
+ String name = remoteChild.getName();
+ remoteSet.put(name, remoteChild);
+ allSet.add(name);
+ }
+ }
+
+ Iterator e = allSet.iterator();
+ while (e.hasNext()) {
+ String keyChildName = (String) e.next();
+
+ if (progress != null) {
+ if (progress.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ // XXX show some progress?
+ }
+
+ IResource localChild =
+ localSet != null ? (IResource) localSet.get(keyChildName) : null;
+
+ IResourceVariant remoteChild =
+ remoteSet != null ? (IResourceVariant) remoteSet.get(keyChildName) : null;
+
+ if (localChild == null) {
+ // there has to be a remote resource available if we got this far
+ Assert.isTrue(remoteChild != null);
+ boolean isContainer = remoteChild.isContainer();
+ localChild = getResourceChild(local /* parent */, keyChildName, isContainer);
+ }
+ mergedResources.put(localChild, remoteChild);
+ }
+ }
+ return mergedResources;
+ }
+
+ /*
+ * Create a local resource handle for a resource variant whose
+ * corresponding local resource does not exist.
+ * @param parent the local parent
+ * @param childName the name of the local resource
+ * @param isContainer the type of resource (file or folder)
+ * @return a local resource handle
+ */
+ private IResource getResourceChild(IResource parent, String childName, boolean isContainer) {
+ if (parent.getType() == IResource.FILE) {
+ return null;
+ }
+ if (isContainer) {
+ return ((IContainer) parent).getFolder(new Path(childName));
+ } else {
+ return ((IContainer) parent).getFile(new Path(childName));
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SessionResourceVariantTree.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SessionResourceVariantTree.java
new file mode 100644
index 000000000..59dacc2b8
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SessionResourceVariantTree.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers.caches;
+
+import java.util.*;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.internal.core.Assert;
+
+/**
+ * A <code>ResourceVariantTree</code> that caches the variant bytes in memory
+ * and does not persist them over workbench invocations.
+ */
+public class SessionResourceVariantTree extends ResourceVariantTree {
+
+ private static final byte[] NO_REMOTE = new byte[0];
+
+ private Map syncBytesCache = new HashMap();
+ private Map membersCache = new HashMap();
+
+ private Map getSyncBytesCache() {
+ return syncBytesCache;
+ }
+
+ private byte[] internalGetSyncBytes(IResource resource) {
+ return (byte[])getSyncBytesCache().get(resource);
+ }
+
+ private void internalAddToParent(IResource resource) {
+ IContainer parent = resource.getParent();
+ if (parent == null) return;
+ List members = (List)membersCache.get(parent);
+ if (members == null) {
+ members = new ArrayList();
+ membersCache.put(parent, members);
+ }
+ members.add(resource);
+ }
+
+ private void internalSetSyncInfo(IResource resource, byte[] bytes) {
+ getSyncBytesCache().put(resource, bytes);
+ internalAddToParent(resource);
+ }
+
+ private void internalRemoveFromParent(IResource resource) {
+ IContainer parent = resource.getParent();
+ List members = (List)membersCache.get(parent);
+ if (members != null) {
+ members.remove(resource);
+ if (members.isEmpty()) {
+ membersCache.remove(parent);
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#dispose()
+ */
+ public void dispose() {
+ syncBytesCache.clear();
+ membersCache.clear();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#getBytes(org.eclipse.core.resources.IResource)
+ */
+ public byte[] getBytes(IResource resource) throws TeamException {
+ byte[] syncBytes = internalGetSyncBytes(resource);
+ if (syncBytes != null && equals(syncBytes, NO_REMOTE)) {
+ // If it is known that there is no remote, return null
+ return null;
+ }
+ return syncBytes;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#setBytes(org.eclipse.core.resources.IResource, byte[])
+ */
+ public boolean setBytes(IResource resource, byte[] bytes) throws TeamException {
+ Assert.isNotNull(bytes);
+ byte[] oldBytes = internalGetSyncBytes(resource);
+ if (oldBytes != null && equals(oldBytes, bytes)) return false;
+ internalSetSyncInfo(resource, bytes);
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#removeBytes(org.eclipse.core.resources.IResource, int)
+ */
+ public boolean removeBytes(IResource resource, int depth) throws TeamException {
+ if (getSyncBytesCache().containsKey(resource)) {
+ if (depth != IResource.DEPTH_ZERO) {
+ IResource[] members = members(resource);
+ for (int i = 0; i < members.length; i++) {
+ IResource child = members[i];
+ removeBytes(child, (depth == IResource.DEPTH_INFINITE) ? IResource.DEPTH_INFINITE: IResource.DEPTH_ZERO);
+ }
+ }
+ getSyncBytesCache().remove(resource);
+ internalRemoveFromParent(resource);
+ return true;
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#members(org.eclipse.core.resources.IResource)
+ */
+ public IResource[] members(IResource resource) {
+ List members = (List)membersCache.get(resource);
+ if (members == null) {
+ return new IResource[0];
+ }
+ return (IResource[]) members.toArray(new IResource[members.size()]);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#isVariantKnown(org.eclipse.core.resources.IResource)
+ */
+ public boolean isVariantKnown(IResource resource) throws TeamException {
+ return internalGetSyncBytes(resource) != null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#setVariantDoesNotExist(org.eclipse.core.resources.IResource)
+ */
+ public boolean setVariantDoesNotExist(IResource resource) throws TeamException {
+ return setBytes(resource, NO_REMOTE);
+ }
+
+ /**
+ * Return <code>true</code> if no bytes are contained in this tree.
+ * @return <code>true</code> if no bytes are contained in this tree.
+ */
+ public boolean isEmpty() {
+ return syncBytesCache.isEmpty();
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SyncTreeSubscriber.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SyncTreeSubscriber.java
new file mode 100644
index 000000000..6b68087d9
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SyncTreeSubscriber.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * 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.team.internal.core.subscribers.caches;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.Subscriber;
+import org.eclipse.team.core.synchronize.*;
+
+/**
+ * A specialization of Subscriber that provides some additional logic for creating
+ * <code>SyncInfo</code> from <code>IResourceVariant</code> instances.
+ * The <code>members()</code> also assumes that remote
+ * instances are stored in the <code>ISynchronizer</code>.
+ */
+public abstract class SyncTreeSubscriber extends Subscriber {
+
+ public abstract IResourceVariant getRemoteResource(IResource resource) throws TeamException;
+
+ public abstract IResourceVariant getBaseResource(IResource resource) throws TeamException;
+
+ /**
+ * Return whether the given local resource has a corresponding remote resource
+ * @param resource the local resource
+ * @return <code>true</code> if the locla resource has a corresponding remote
+ */
+ protected abstract boolean hasRemote(IResource resource) throws TeamException;
+
+ public SyncInfo getSyncInfo(IResource resource) throws TeamException {
+ if (!isSupervised(resource)) return null;
+ IResourceVariant remoteResource = getRemoteResource(resource);
+ IResourceVariant baseResource;
+ if (getResourceComparator().isThreeWay()) {
+ baseResource= getBaseResource(resource);
+ } else {
+ baseResource = null;
+ }
+ return getSyncInfo(resource, baseResource, remoteResource);
+ }
+
+ /**
+ * @return
+ */
+ public abstract IResourceVariantComparator getResourceComparator();
+
+ /**
+ * Method that creates an instance of SyncInfo for the provider local, base and remote.
+ * Can be overiden by subclasses.
+ * @param local
+ * @param base
+ * @param remote
+ * @param monitor
+ * @return
+ */
+ protected SyncInfo getSyncInfo(IResource local, IResourceVariant base, IResourceVariant remote) throws TeamException {
+ SyncInfo info = new SyncInfo(local, base, remote, this.getResourceComparator());
+ info.init();
+ return info;
+ }
+
+ public IResource[] members(IResource resource) throws TeamException {
+ if(resource.getType() == IResource.FILE) {
+ return new IResource[0];
+ }
+ try {
+ // Filter and return only phantoms associated with the remote synchronizer.
+ IResource[] members;
+ try {
+ members = ((IContainer)resource).members(true /* include phantoms */);
+ } catch (CoreException e) {
+ if (!isSupervised(resource) || e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND) {
+ // The resource is no longer supervised or doesn't exist in any form
+ // so ignore the exception and return that there are no members
+ return new IResource[0];
+ }
+ throw e;
+ }
+ List filteredMembers = new ArrayList(members.length);
+ for (int i = 0; i < members.length; i++) {
+ IResource member = members[i];
+
+ // TODO: consider that there may be several sync states on this resource. There
+ // should instead be a method to check for the existance of a set of sync types on
+ // a resource.
+ if(member.isPhantom() && !hasRemote(member)) {
+ continue;
+ }
+
+ // TODO: Is this a valid use of isSupervised
+ if (isSupervised(resource)) {
+ filteredMembers.add(member);
+ }
+ }
+ return (IResource[]) filteredMembers.toArray(new IResource[filteredMembers.size()]);
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+
+}

Back to the top