diff options
author | Jean Michel-Lemieux | 2004-02-25 20:45:35 +0000 |
---|---|---|
committer | Jean Michel-Lemieux | 2004-02-25 20:45:35 +0000 |
commit | 611f26b53dc8c8e03019aa05a42e7ed40ad2fa51 (patch) | |
tree | da1a682b00dda95024364683fd6e75f19c4505fe | |
parent | 4a1ca77df6f4238416a8715a8c35819611d5992a (diff) | |
download | eclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.tar.gz eclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.tar.xz eclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.zip |
SyncView API released to HEAD.
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> + * >extension + point="org.eclipse.team.core.deployment"< + >deployment + name="Example Deployment Provider" + class="org.eclipse.team.internal.example.DeploymentProviderClass" + id="org.eclipse.team.example.DeploymentProvider"< + >/deployment< + >/extension< + </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> </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); + } + } + +} |