| /******************************************************************************* |
| * 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.variants; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.internal.core.Messages; |
| import org.eclipse.team.internal.core.Policy; |
| import org.eclipse.team.internal.core.TeamPlugin; |
| |
| /** |
| * An implementation of <code>IResourceVariantTree</code> that provides the logic for |
| * refreshing the tree and collecting the results so they can be cached locally. |
| * This class does not perform the caching but relies on subclasses to do that by |
| * overriding the <code>setVariant</code> method. The subclass |
| * {@link ResourceVariantTree} does provide caching. |
| * |
| * @see IResourceVariantTree |
| * @see ResourceVariantTree |
| * |
| * @since 3.0 |
| */ |
| public abstract class AbstractResourceVariantTree implements IResourceVariantTree { |
| |
| /** |
| * 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 variants have changed |
| * @throws TeamException if an error occurs |
| */ |
| @Override |
| public IResource[] refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException { |
| List<IResource> changedResources = new ArrayList<>(); |
| monitor.beginTask(null, 100 * resources.length); |
| for (IResource resource : resources) { |
| IResource[] changed = refresh(resource, depth, Policy.subMonitorFor(monitor, 100)); |
| changedResources.addAll(Arrays.asList(changed)); |
| } |
| monitor.done(); |
| return 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>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 resource 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 if an error occurs |
| */ |
| protected IResource[] refresh(IResource resource, int depth, IProgressMonitor monitor) throws TeamException { |
| IResource[] changedResources = null; |
| monitor.beginTask(null, 100); |
| try { |
| monitor.setTaskName(NLS.bind(Messages.SynchronizationCacheRefreshOperation_0, new String[] { resource.getFullPath().makeRelative().toString() })); |
| |
| // 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, Policy.subMonitorFor(sub, 64)); |
| } finally { |
| sub.done(); |
| } |
| } finally { |
| 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 if an error occurs |
| */ |
| protected IResource[] collectChanges(IResource local, IResourceVariant remote, int depth, IProgressMonitor monitor) throws TeamException { |
| List<IResource> changedResources = new ArrayList<>(); |
| collectChanges(local, remote, changedResources, depth, monitor); |
| return changedResources.toArray(new IResource[changedResources.size()]); |
| } |
| |
| /** |
| * Fetch the members of the given resource variant handle. This method may |
| * return members that were fetched when <code>fetchVariant</code> was called or |
| * may fetch the children directly (i.e. this method may contact the server). |
| * @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; |
| |
| /** |
| * 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 descendants will be traversed. |
| * This method may pre-fetch 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>fetchMembers(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; |
| |
| /** |
| * Method that is invoked during collection to let subclasses know which members |
| * were collected for the given resource. Implementors should purge any cached |
| * state for children of the local resource that are no longer members. Any such resources |
| * should be returned to allow clients to clear any state they maintain for those resources. |
| * @param local the local resource |
| * @param members the collected members |
| * @return any resources that were previously collected whose state has been flushed |
| */ |
| protected IResource[] collectedMembers(IResource local, IResource[] members) throws TeamException { |
| return new IResource[0]; |
| } |
| |
| /** |
| * Set the variant associated with the local resource to the newly fetched resource |
| * variant. This method is invoked during change collection and should return whether |
| * the variant associated with the local resource has changed |
| * @param local the local resource |
| * @param remote the newly fetched resource variant |
| * @return <code>true</code> if the resource variant changed |
| * @throws TeamException if an error occurs |
| */ |
| protected abstract boolean setVariant(IResource local, IResourceVariant remote) throws TeamException; |
| |
| private void collectChanges(IResource local, IResourceVariant remote, Collection<IResource> changedResources, int depth, IProgressMonitor monitor) throws TeamException { |
| boolean changed = setVariant(local, remote); |
| if (changed) { |
| changedResources.add(local); |
| } |
| if (depth == IResource.DEPTH_ZERO) return; |
| Map<IResource, IResourceVariant> children = mergedMembers(local, remote, monitor); |
| for (IResource localChild : children.keySet()) { |
| IResourceVariant remoteChild = children.get(localChild); |
| collectChanges(localChild, remoteChild, changedResources, |
| depth == IResource.DEPTH_INFINITE ? IResource.DEPTH_INFINITE : IResource.DEPTH_ZERO, |
| monitor); |
| } |
| |
| IResource[] cleared = collectedMembers(local, children.keySet().toArray(new IResource[children.keySet().size()])); |
| changedResources.addAll(Arrays.asList(cleared)); |
| monitor.worked(1); |
| } |
| |
| private Map<IResource, IResourceVariant> mergedMembers(IResource local, IResourceVariant remote, IProgressMonitor progress) throws TeamException { |
| |
| // {IResource -> IResourceVariant} |
| Map<IResource, IResourceVariant> 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<String> allSet = new HashSet<>(20); |
| Map<String, IResource> localSet = null; |
| Map<String, IResourceVariant> remoteSet = null; |
| |
| if (localChildren.length > 0) { |
| localSet = new HashMap<>(10); |
| for (IResource localChild : localChildren) { |
| String name = localChild.getName(); |
| localSet.put(name, localChild); |
| allSet.add(name); |
| } |
| } |
| |
| if (remoteChildren.length > 0) { |
| remoteSet = new HashMap<>(10); |
| for (IResourceVariant remoteChild : remoteChildren) { |
| String name = remoteChild.getName(); |
| remoteSet.put(name, remoteChild); |
| allSet.add(name); |
| } |
| } |
| |
| for (String keyChildName : allSet) { |
| Policy.checkCanceled(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); |
| } |
| if (localChild == null) { |
| TeamPlugin.log(IStatus.ERROR, NLS.bind("File {0} cannot be the parent of remote resource {1}", //$NON-NLS-1$ |
| new Object[] { local.getFullPath(), keyChildName }), null); |
| } else { |
| mergedResources.put(localChild, remoteChild); |
| } |
| } |
| } |
| return mergedResources; |
| } |
| |
| private IResource getResourceChild(IResource parent, String childName, boolean isContainer) { |
| if (parent.getType() == IResource.FILE) { |
| return null; |
| } |
| if (isContainer) { |
| return ((IContainer) parent).getFolder(new Path(null, childName)); |
| } else { |
| return ((IContainer) parent).getFile(new Path(null, childName)); |
| } |
| } |
| |
| } |