| /******************************************************************************* |
| * 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 class ResourceVariantTreeRefresh { |
| |
| private IResourceVariantFactory factory; |
| private ResourceVariantTree tree; |
| |
| /** |
| * Create the refresh operation with the give variant factory. |
| * @param factory |
| */ |
| public ResourceVariantTreeRefresh(IResourceVariantFactory factory, ResourceVariantTree tree) { |
| this.factory = factory; |
| this.tree = tree; |
| } |
| |
| /** |
| * 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 root = factory.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, root, 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 |
| */ |
| public 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()]); |
| } |
| |
| /** |
| * 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 { |
| byte[] newRemoteBytes = factory.getBytes(local, remote); |
| boolean changed; |
| if (newRemoteBytes == null) { |
| changed = tree.setVariantDoesNotExist(local); |
| } else { |
| changed = tree.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 |
| 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 |
| tree.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 (tree.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 = factory.fetchMembers(remote, progress); |
| } |
| |
| |
| IResource[] localChildren = tree.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)); |
| } |
| } |
| } |