/******************************************************************************* * Copyright (c) 2000, 2017 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.team.core; import java.net.URI; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFileModificationValidator; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IProjectNature; import org.eclipse.core.resources.IProjectNatureDescriptor; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceRuleFactory; import org.eclipse.core.resources.IResourceStatus; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.team.FileModificationValidationContext; import org.eclipse.core.resources.team.FileModificationValidator; import org.eclipse.core.resources.team.IMoveDeleteHook; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; 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.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.ILock; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.osgi.util.NLS; import org.eclipse.team.core.history.IFileHistoryProvider; import org.eclipse.team.core.subscribers.Subscriber; import org.eclipse.team.internal.core.Messages; import org.eclipse.team.internal.core.PessimisticResourceRuleFactory; import org.eclipse.team.internal.core.RepositoryProviderManager; import org.eclipse.team.internal.core.TeamHookDispatcher; import org.eclipse.team.internal.core.TeamPlugin; /** * A concrete subclass of RepositoryProvider is created for each * project that is associated with a repository provider. The lifecycle of these * instances is is similar to that of the platform's 'nature' mechanism. *

* To create a repository provider and have it registered with the platform, a * client must minimally: *

*
    *
  1. extend RepositoryProvider *
  2. define a repository extension in plugin.xml. Here is an * example extension point definition: * * *
    <extension point="org.eclipse.team.core.repository"> *
     <repository *
      class="org.eclipse.myprovider.MyRepositoryProvider" *
      id="org.eclipse.myprovider.myProviderID"> *
     </repository> *
    </extension> *
    *
*

* Once a repository provider is registered with Team, then you can associate a * repository provider with a project by invoking * RepositoryProvider.map(). *

* * @see RepositoryProvider#map(IProject, String) * * @since 2.0 */ public abstract class RepositoryProvider implements IProjectNature, IAdaptable { private final static String TEAM_SETID = "org.eclipse.team.repository-provider"; //$NON-NLS-1$ private final static List AllProviderTypeIds = initializeAllProviderTypes(); // the project instance that this nature is assigned to private IProject project; // lock to ensure that map/unmap and getProvider support concurrency private static final ILock mappingLock = Job.getJobManager().newLock(); // Session property used to identify projects that are not mapped private static final Object NOT_MAPPED = new Object(); /** * Instantiate a new RepositoryProvider with concrete class by given providerID * and associate it with project. * * @param project the project to be mapped * @param id the ID of the provider to be mapped to the project * @throws TeamException if * * @see RepositoryProvider#unmap(IProject) */ public static void map(IProject project, String id) throws TeamException { ISchedulingRule rule = ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(project); try { // Obtain a scheduling rule on the project before obtaining the // mappingLock. This is required because a caller of getProvider // may hold a scheduling rule before getProvider is invoked but // getProvider itself does not (and can not) obtain a scheduling rule. // Thus, the locking order is always scheduling rule followed by // mappingLock. Job.getJobManager().beginRule(rule, null); try { mappingLock.acquire(); RepositoryProvider existingProvider = null; if(project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY) != null) existingProvider = getProvider(project); // get the real one, not the nature one //if we already have a provider, and its the same ID, we're ok //if the ID's differ, unmap the existing. if(existingProvider != null) { if(existingProvider.getID().equals(id)) return; //nothing to do else unmap(project); } // Create the provider as a session property before adding the persistent // property to ensure that the provider can be instantiated RepositoryProvider provider = mapNewProvider(project, id); //mark it with the persistent ID for filtering try { project.setPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY, id); } catch (CoreException outer) { // couldn't set the persistent property so clear the session property try { project.setSessionProperty(TeamPlugin.PROVIDER_PROP_KEY, null); } catch (CoreException inner) { // something is seriously wrong TeamPlugin.log(IStatus.ERROR, NLS.bind(Messages.RepositoryProvider_couldNotClearAfterError, new String[] { project.getName(), id }), inner); } throw outer; } provider.configure(); //adding the nature would've caused project description delta, so trigger one project.touch(null); // Set the rule factory for the provider after the touch // so the touch does not fail due to incompatible modify rules TeamHookDispatcher.setProviderRuleFactory(project, provider.getRuleFactory()); // Notify any listeners RepositoryProviderManager.getInstance().providerMapped(provider); } finally { mappingLock.release(); } } catch (CoreException e) { throw TeamPlugin.wrapException(e); } finally { Job.getJobManager().endRule(rule); } } /* * Instantiate the provider denoted by ID and store it in the session property. * Return the new provider instance. If a TeamException is thrown, it is * guaranteed that the session property will not be set. * * @param project * @param id * @return RepositoryProvider * @throws TeamException we can't instantiate the provider, or if the set * session property fails from core */ private static RepositoryProvider mapNewProvider(final IProject project, final String id) throws TeamException { final RepositoryProvider provider = newProvider(id); // instantiate via extension point if(provider == null) throw new TeamException(NLS.bind(Messages.RepositoryProvider_couldNotInstantiateProvider, new String[] { project.getName(), id })); // validate that either the provider supports linked resources or the project has no linked resources if (!provider.canHandleLinkedResourceURI()) { try { project.accept(proxy -> { if (proxy.isLinked()) { if (!provider.canHandleLinkedResources() || proxy.requestFullPath().segmentCount() > 2 || !EFS.SCHEME_FILE.equals(proxy.requestResource().getLocationURI().getScheme())) throw new TeamException(new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.LINKING_NOT_ALLOWED, NLS.bind(Messages.RepositoryProvider_linkedURIsExist, new String[] { project.getName(), id }), null)); } return true; }, IResource.NONE); } catch (CoreException e) { if (e instanceof TeamException) { TeamException te = (TeamException) e; throw te; } throw new TeamException(e); } } if (!provider.canHandleLinkedResources()) { try { IResource[] members = project.members(); for (IResource resource : members) { if (resource.isLinked()) { throw new TeamException(new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.LINKING_NOT_ALLOWED, NLS.bind(Messages.RepositoryProvider_linkedResourcesExist, new String[] { project.getName(), id }), null)); } } } catch (CoreException e) { throw TeamPlugin.wrapException(e); } } //store provider instance as session property try { project.setSessionProperty(TeamPlugin.PROVIDER_PROP_KEY, provider); provider.setProject(project); } catch (CoreException e) { throw TeamPlugin.wrapException(e); } return provider; } private static RepositoryProvider mapExistingProvider(IProject project, String id) throws TeamException { try { // Obtain the mapping lock before creating the instance so we can make sure // that a disconnect is not happening at the same time mappingLock.acquire(); try { // Ensure that the persistent property is still set // (i.e. an unmap may have come in since we checked it last String currentId = project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY); if (currentId == null) { // The provider has been unmapped return null; } if (!currentId.equals(id)) { // A provider has been disconnected and another connected // Since mapping creates the session property, we // can just return it return lookupProviderProp(project); } } catch (CoreException e) { throw TeamPlugin.wrapException(e); } return mapNewProvider(project, id); } finally { mappingLock.release(); } } /** * Disassociates project with the repository provider its currently mapped to. * @param project * @throws TeamException The project isn't associated with any repository provider. */ public static void unmap(IProject project) throws TeamException { ISchedulingRule rule = ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(project); try{ // See the map(IProject, String) method for a description of lock ordering Job.getJobManager().beginRule(rule, null); try { mappingLock.acquire(); String id = project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY); //If you tried to remove a non-existant nature it would fail, so we need to as well with the persistent prop if(id == null) { throw new TeamException(NLS.bind(Messages.RepositoryProvider_No_Provider_Registered, new String[] { project.getName() })); } //This will instantiate one if it didn't already exist, //which is ok since we need to call deconfigure() on it for proper lifecycle RepositoryProvider provider = getProvider(project); if (provider == null) { // There is a persistent property but the provider cannot be obtained. // The reason could be that the provider's plugin is no longer available. // Better log it just in case this is unexpected. TeamPlugin.log(IStatus.ERROR, NLS.bind(Messages.RepositoryProvider_couldNotInstantiateProvider, new String[] { project.getName(), id }), null); } if (provider != null) provider.deconfigure(); project.setSessionProperty(TeamPlugin.PROVIDER_PROP_KEY, null); project.setPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY, null); if (provider != null) provider.deconfigured(); //removing the nature would've caused project description delta, so trigger one project.touch(null); // Change the rule factory after the touch in order to // avoid rule incompatibility TeamHookDispatcher.setProviderRuleFactory(project, null); // Notify any listeners RepositoryProviderManager.getInstance().providerUnmapped(project); } finally { mappingLock.release(); } } catch (CoreException e) { throw TeamPlugin.wrapException(e); } finally { Job.getJobManager().endRule(rule); } } /* * Return the provider mapped to project, or null if none; */ private static RepositoryProvider lookupProviderProp(IProject project) throws CoreException { Object provider = project.getSessionProperty(TeamPlugin.PROVIDER_PROP_KEY); if (provider instanceof RepositoryProvider) { return (RepositoryProvider) provider; } return null; } /** * Default constructor required for the resources plugin to instantiate this class from * the nature extension definition. */ public RepositoryProvider() { } /** * Configures the provider for the given project. This method is called after setProject. * If an exception is generated during configuration * of the project, the provider will not be assigned to the project. * * @throws CoreException if the configuration fails. */ abstract public void configureProject() throws CoreException; /** * Configures the nature for the given project. This is called by RepositoryProvider.map() * the first time a provider is mapped to a project. It is not intended to be called by clients. * * @throws CoreException if this method fails. If the configuration fails the provider will not be * associated with the project. * * @see RepositoryProvider#configureProject() */ @Override final public void configure() throws CoreException { try { configureProject(); } catch(CoreException e) { try { RepositoryProvider.unmap(getProject()); } catch(TeamException e2) { throw new CoreException(new Status(IStatus.ERROR, TeamPlugin.ID, 0, Messages.RepositoryProvider_Error_removing_nature_from_project___1 + getID(), e2)); } throw e; } } /** * Method deconfigured is invoked after a provider has been unmaped. The * project will no longer have the provider associated with it when this * method is invoked. It is a last chance for the provider to clean up. */ protected void deconfigured() { } /** * Answer the id of this provider instance. The id should be the repository provider's * id as defined in the provider plugin's plugin.xml. * * @return the nature id of this provider */ abstract public String getID(); /** * Returns an IFileModificationValidator for pre-checking operations * that modify the contents of files. * Returns null if the provider does not wish to participate in * file modification validation. * @return an IFileModificationValidator for pre-checking operations * that modify the contents of files * * @see org.eclipse.core.resources.IFileModificationValidator * @deprecated use {@link #getFileModificationValidator2()} */ @Deprecated public IFileModificationValidator getFileModificationValidator() { return null; } /** * Returns a {@link FileModificationValidator} for pre-checking operations * that modify the contents of files. Returns null if the * provider does not wish to participate in file modification validation. By * default, this method wraps the old validator returned from * {@link #getFileModificationValidator()}. Subclasses that which to remain * backwards compatible while providing this new API should override * {@link #getFileModificationValidator2()} to return a subclass of * {@link FileModificationValidator} and should return the same * validator from {@link #getFileModificationValidator()}. *

* This method is not intended to be called by clients. Clients should * use the {@link IWorkspace#validateEdit(IFile[], Object)} method instead. * * @return an FileModificationValidator for pre-checking * operations that modify the contents of files * * @see FileModificationValidator * @see IWorkspace#validateEdit(IFile[], Object) * @since 3.3 */ public FileModificationValidator getFileModificationValidator2() { final IFileModificationValidator fileModificationValidator = getFileModificationValidator(); if (fileModificationValidator == null) return null; return new FileModificationValidator() { @Override public IStatus validateSave(IFile file) { return fileModificationValidator.validateSave(file); } @Override public IStatus validateEdit(IFile[] files, FileModificationValidationContext context) { // Extract the shell from the context in order to invoke the old API Object shell; if (context == null) shell = null; else shell = context.getShell(); return fileModificationValidator.validateEdit(files, shell); } }; } /** * Returns an IFileHistoryProvider which can be used to access * file histories. By default, returns null. Subclasses may override. * @return an IFileHistoryProvider which can be used to access * file histories. * @since 3.2 */ public IFileHistoryProvider getFileHistoryProvider(){ return null; } /** * Returns an IMoveDeleteHook for handling moves and deletes * that occur within projects managed by the provider. This allows providers * to control how moves and deletes occur and includes the ability to prevent them. *

* Returning null signals that the default move and delete behavior is desired. * @return an IMoveDeleteHook for handling moves and deletes * that occur within projects managed by the provider * * @see org.eclipse.core.resources.team.IMoveDeleteHook */ public IMoveDeleteHook getMoveDeleteHook() { return null; } /** * Returns a brief description of this provider. The exact details of the * representation are unspecified and subject to change, but the following * may be regarded as typical: * * "SampleProject:org.eclipse.team.cvs.provider" * * @return a string description of this provider */ @Override public String toString() { return NLS.bind(Messages.RepositoryProvider_toString, new String[] { getProject().getName(), getID() }); } /** * Returns all known (registered) RepositoryProvider ids. * * @return an array of registered repository provider ids. */ final public static String[] getAllProviderTypeIds() { IProjectNatureDescriptor[] desc = ResourcesPlugin.getWorkspace().getNatureDescriptors(); Set teamSet = new HashSet<>(); teamSet.addAll(AllProviderTypeIds); // add in all the ones we know via extension point //fall back to old method of nature ID to find any for backwards compatibility for (IProjectNatureDescriptor d : desc) { String[] setIds = d.getNatureSetIds(); for (String setId : setIds) { if (setId.equals(TEAM_SETID)) { teamSet.add(d.getNatureId()); } } } return teamSet.toArray(new String[teamSet.size()]); } /** * Returns the provider for a given IProject or null if a provider is not associated with * the project or if the project is closed or does not exist. This method should be called if the caller * is looking for any repository provider. Otherwise call getProvider(project, id) * to look for a specific repository provider type. * @param project the project to query for a provider * @return the repository provider associated with the project */ final public static RepositoryProvider getProvider(IProject project) { try { if (project.isAccessible()) { //----------------------------- //First, look for the session property RepositoryProvider provider = lookupProviderProp(project); if(provider != null) return provider; // Do a quick check to see it the project is known to be unshared. // This is done to avoid accessing the persistent property store if (isMarkedAsUnshared(project)) return null; // ----------------------------- //Next, check if it has the ID as a persistent property, if yes then instantiate provider String id = project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY); if(id != null) return mapExistingProvider(project, id); //Couldn't find using new method, fall back to lookup using natures for backwards compatibility //----------------------------- IProjectDescription projectDesc = project.getDescription(); String[] natureIds = projectDesc.getNatureIds(); IWorkspace workspace = ResourcesPlugin.getWorkspace(); // for every nature id on this project, find it's natures sets and check if it is // in the team set. for (String natureId : natureIds) { IProjectNatureDescriptor desc = workspace.getNatureDescriptor(natureId); // The descriptor can be null if the nature doesn't exist if (desc != null) { String[] setIds = desc.getNatureSetIds(); for (String setId : setIds) { if (setId.equals(TEAM_SETID)) { return getProvider(project, natureId); } } } } markAsUnshared(project); } } catch(CoreException e) { if (!isAcceptableException(e)) { TeamPlugin.log(e); } markAsUnshared(project); } return null; } /* * Return whether the given exception is acceptable during a getProvider(). * If the exception is acceptable, it is assumed that there is no provider * on the project. */ private static boolean isAcceptableException(CoreException e) { return e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND; } /** * Returns a provider of type with the given id if associated with the given project * or null if the project is not associated with a provider of that type * or the nature id is that of a non-team repository provider nature. * * @param project the project to query for a provider * @param id the repository provider id * @return the repository provider */ final public static RepositoryProvider getProvider(IProject project, String id) { try { if (project.isAccessible()) { // Look for an existing provider first to avoid accessing persistent properties RepositoryProvider provider = lookupProviderProp(project); //throws core, we will reuse the catching already here if(provider != null) { if (provider.getID().equals(id)) { return provider; } else { return null; } } // Do a quick check to see it the project is known to be unshared. // This is done to avoid accessing the persistent property store if (isMarkedAsUnshared(project)) return null; // There isn't one so check the persistent property String existingID = project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY); if(id.equals(existingID)) { // The ids are equal so instantiate and return RepositoryProvider newProvider = mapExistingProvider(project, id); if (newProvider!= null && newProvider.getID().equals(id)) { return newProvider; } else { // The id changed before we could create the desired provider return null; } } //couldn't find using new method, fall back to lookup using natures for backwards compatibility //----------------------------- // if the nature id given is not in the team set then return // null. IProjectNatureDescriptor desc = ResourcesPlugin.getWorkspace().getNatureDescriptor(id); if(desc == null) //for backwards compatibility, may not have any nature by that ID return null; String[] setIds = desc.getNatureSetIds(); for (String setId : setIds) { if (setId.equals(TEAM_SETID)) { return (RepositoryProvider)project.getNature(id); } } markAsUnshared(project); } } catch(CoreException e) { if (!isAcceptableException(e)) { TeamPlugin.log(e); } markAsUnshared(project); } return null; } /** * Returns whether the given project is shared or not. This is a lightweight * method in that it will not instantiate a provider instance (as * getProvider would) if one is not already instantiated. * * Note that IProject.touch() generates a project description delta. This, in combination * with isShared() can be used to be notified of sharing/unsharing of projects. * * @param project the project being tested. * @return boolean * * @see #getProvider(IProject) * * @since 2.1 */ public static boolean isShared(IProject project) { if (!project.isAccessible()) return false; try { if (lookupProviderProp(project) != null) return true; // Do a quick check to see it the project is known to be unshared. // This is done to avoid accessing the persistent property store if (isMarkedAsUnshared(project)) return false; boolean shared = project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY) != null; if (!shared) markAsUnshared(project); return shared; } catch (CoreException e) { TeamPlugin.log(e); return false; } } private static boolean isMarkedAsUnshared(IProject project) { try { return project.getSessionProperty(TeamPlugin.PROVIDER_PROP_KEY) == NOT_MAPPED; } catch (CoreException e) { return false; } } private static void markAsUnshared(IProject project) { try { project.setSessionProperty(TeamPlugin.PROVIDER_PROP_KEY, NOT_MAPPED); } catch (CoreException e) { // Just ignore the error as this is just an optimization } } @Override public IProject getProject() { return project; } @Override public void setProject(IProject project) { this.project = project; } private static List initializeAllProviderTypes() { List allIDs = new ArrayList<>(); TeamPlugin plugin = TeamPlugin.getPlugin(); if (plugin != null) { IExtensionPoint extension = Platform.getExtensionRegistry().getExtensionPoint(TeamPlugin.ID, TeamPlugin.REPOSITORY_EXTENSION); if (extension != null) { IExtension[] extensions = extension.getExtensions(); for (IExtension e : extensions) { IConfigurationElement[] configElements = e.getConfigurationElements(); for (IConfigurationElement configElement : configElements) { String extensionId = configElement.getAttribute("id"); //$NON-NLS-1$ allIDs.add(extensionId); } } } } return allIDs; } private static RepositoryProvider newProvider(String id) { TeamPlugin plugin = TeamPlugin.getPlugin(); if (plugin != null) { IExtensionPoint extension = Platform.getExtensionRegistry().getExtensionPoint(TeamPlugin.ID, TeamPlugin.REPOSITORY_EXTENSION); if (extension != null) { IExtension[] extensions = extension.getExtensions(); for (IExtension ext : extensions) { IConfigurationElement[] configElements = ext.getConfigurationElements(); for (IConfigurationElement configElement : configElements) { String extensionId = configElement.getAttribute("id"); //$NON-NLS-1$ if (extensionId != null && extensionId.equals(id)) { try { return (RepositoryProvider) configElement.createExecutableExtension("class"); //$NON-NLS-1$ } catch (CoreException e) { TeamPlugin.log(e); } catch (ClassCastException e) { String className = configElement.getAttribute("class"); //$NON-NLS-1$ TeamPlugin.log(IStatus.ERROR, NLS.bind(Messages.RepositoryProvider_invalidClass, new String[] { id, className }), e); } return null; } } } } } return null; } /** * 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 * subclasses (although it is possible to do so in special cases). * Subclasses can indicate that they support linked resources by overriding * the canHandleLinkedResources() method. * * @param resource see org.eclipse.core.resources.team.TeamHook * @param updateFlags see org.eclipse.core.resources.team.TeamHook * @param location see org.eclipse.core.resources.team.TeamHook * @return IStatus see org.eclipse.core.resources.team.TeamHook * * @see RepositoryProvider#canHandleLinkedResources() * * @deprecated see {@link #validateCreateLink(IResource, int, URI) } instead * @since 2.1 */ @Deprecated public IStatus validateCreateLink(IResource resource, int updateFlags, IPath location) { if (canHandleLinkedResources()) { return Team.OK_STATUS; } else { return new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.LINKING_NOT_ALLOWED, NLS.bind(Messages.RepositoryProvider_linkedResourcesNotSupported, new String[] { getProject().getName(), getID() }), null); } } /** * 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 * subclasses (although it is possible to do so in special cases). * Subclasses can indicate that they support linked resources by overriding * the canHandleLinkedResourcesAtArbitraryDepth() method. * * @param resource see org.eclipse.core.resources.team.TeamHook * @param updateFlags see org.eclipse.core.resources.team.TeamHook * @param location see org.eclipse.core.resources.team.TeamHook * @return IStatus see org.eclipse.core.resources.team.TeamHook * * @see RepositoryProvider#canHandleLinkedResourceURI() * * @since 3.2 */ public IStatus validateCreateLink(IResource resource, int updateFlags, URI location) { if (resource.getProjectRelativePath().segmentCount() == 1 && EFS.SCHEME_FILE.equals(location.getScheme())) { // This is compatible with the old style link so invoke the old // validateLink return validateCreateLink(resource, updateFlags, URIUtil.toPath(location)); } if (canHandleLinkedResourceURI()) { return Team.OK_STATUS; } else { return new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.LINKING_NOT_ALLOWED, NLS.bind(Messages.RepositoryProvider_linkedURIsNotSupported, new String[] { getProject().getName(), getID() }), null); } } /** * Method canHandleLinkedResources should be overridden by subclasses who * support linked resources. At a minimum, supporting linked resources * requires changes to the move/delete hook * {@link org.eclipse.core.resources.team.IMoveDeleteHook}. This method is * called after the RepositoryProvider is instantiated but before * setProject() is invoked so it will not have access to any * state determined from the setProject() method. * @return boolean * * @see org.eclipse.core.resources.team.IMoveDeleteHook * * @since 2.1 * * @deprecated see {@link #canHandleLinkedResourceURI() } */ @Deprecated public boolean canHandleLinkedResources() { return canHandleLinkedResourceURI(); } /** * Return whether this repository provider can handle linked resources that * are located via a URI (i.e. may not be on the local file system) or occur * at an arbitrary depth in the project. This should be overridden by * subclasses who support linked resources at arbitrary depth and/or in * non-local file systems. This is not enabled by default since linked * resources previously only occurred at the root of a project but now can * occur anywhere within a project. This method is called after the * RepositoryProvider is instantiated but before setProject() * is invoked so it will not have access to any state determined from the * setProject() method. * * @return whether this repository provider can handle linked resources that * are located via a URI or occur at an arbitrary depth in the * project * * @see #validateCreateLink(IResource, int, URI) * * @since 3.2 */ public boolean canHandleLinkedResourceURI() { return false; } @Override public T getAdapter(Class adapter) { return null; } /** * Return the resource rule factory for this provider. This factory * will be used to determine the scheduling rules that are to be obtained * when performing various resource operations (e.g. move, copy, delete, etc.) * on the resources in the project the provider is mapped to. *

* By default, the factory returned by this method is pessimistic and * obtains the workspace lock for all operations that could result in a * callback to the provider (either through the IMoveDeleteHook * or IFileModificationValidator). This is done to ensure that * older providers are not broken. However, providers should override this * method and provide a subclass of {@link org.eclipse.core.resources.team.ResourceRuleFactory} * that provides rules of a more optimistic granularity (e.g. project * or lower). * @return the rule factory for this provider * @since 3.0 * @see org.eclipse.core.resources.team.ResourceRuleFactory */ public IResourceRuleFactory getRuleFactory() { return new PessimisticResourceRuleFactory(); } /** * Return a {@link Subscriber} that describes the synchronization state * of the resources contained in the project associated with this * provider. The subscriber is obtained from the {@link RepositoryProviderType} * associated with a provider and is thus shared for all providers of the * same type. * @return a subscriber that provides resource synchronization state or null * @since 3.2 */ public final Subscriber getSubscriber() { RepositoryProviderType type = RepositoryProviderType.getProviderType(getID()); if (type != null) return type.getSubscriber(); return null; } }