Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean Michel-Lemieux2004-02-25 20:45:35 +0000
committerJean Michel-Lemieux2004-02-25 20:45:35 +0000
commit611f26b53dc8c8e03019aa05a42e7ed40ad2fa51 (patch)
treeda1a682b00dda95024364683fd6e75f19c4505fe /bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core
parent4a1ca77df6f4238416a8715a8c35819611d5992a (diff)
downloadeclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.tar.gz
eclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.tar.xz
eclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.zip
SyncView API released to HEAD.
Diffstat (limited to 'bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core')
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/BackgroundEventHandler.java362
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DefaultFileModificationValidator.java13
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DeploymentProviderManager.java447
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IJobListener.java26
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IMemento.java169
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java250
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCacheEntry.java217
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/Sorter.java67
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/TeamPlugin.java1
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/XMLMemento.java406
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/messages.properties18
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderDescriptor.java80
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderRegistry.java56
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/RegistryReader.java144
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/ContentComparator.java140
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberEventHandler.java431
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberSyncInfoSet.java80
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoStatistics.java107
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoTreeChangeEvent.java95
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoWorkingSetFilter.java111
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetChangedEvent.java104
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInput.java93
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSubscriber.java57
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSyncSet.java117
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/WorkingSetSyncSetInput.java36
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/DescendantResourceVariantTree.java148
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/PersistantResourceVariantTree.java160
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTree.java129
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTreeRefresh.java339
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SessionResourceVariantTree.java149
-rw-r--r--bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SyncTreeSubscriber.java112
31 files changed, 4656 insertions, 8 deletions
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/BackgroundEventHandler.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/BackgroundEventHandler.java
new file mode 100644
index 000000000..876e6fe64
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/BackgroundEventHandler.java
@@ -0,0 +1,362 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.core.runtime.jobs.JobChangeAdapter;
+import org.eclipse.team.core.TeamException;
+
+/**
+ * This class provides the infrastucture for processing/dispatching of events using a
+ * background job. This is useful in the following situations.
+ * <ul>
+ * <li>an operation is potentially long running but a resposive UI is desired
+ * while the operation is being performed.</li>
+ * <li>a change is a POST_CHANGE delta requires further modifications to the workspace
+ * which cannot be performed in the delta handler because the workspace is locked.</li>
+ * <li>a data structure is not thread safe and requires serialized operations.<li>
+ * </ul>
+ * </p>
+ * @since 3.0
+ */
+public abstract class BackgroundEventHandler {
+
+ // Events that need to be processed
+ private List awaitingProcessing = new ArrayList();
+
+ // The job that runs when events need to be processed
+ private Job eventHandlerJob;
+
+ // Indicate if the event handler has been shutdown
+ private boolean shutdown;
+
+ // Accumulate exceptions that occur
+ private ExceptionCollector errors;
+
+ // time the last dispath took
+ private long processingEventsDuration = 0L;
+
+ // time between event dispatches
+ private long DISPATCH_DELAY = 1500;
+
+ // time to wait for messages to be queued
+ private long WAIT_DELAY = 1000;
+
+ private String jobName;
+
+ /**
+ * Resource event class. The type is specific to subclasses.
+ */
+ public static class Event {
+ IResource resource;
+ int type;
+ int depth;
+ public Event(IResource resource, int type, int depth) {
+ this.resource = resource;
+ this.type = type;
+ this.depth = depth;
+ }
+ public int getDepth() {
+ return depth;
+ }
+ public IResource getResource() {
+ return resource;
+ }
+ public int getType() {
+ return type;
+ }
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("resource: "); //$NON-NLS-1$
+ buffer.append(resource.getFullPath());
+ buffer.append(" type: "); //$NON-NLS-1$
+ buffer.append(getTypeString());
+ buffer.append(" depth: "); //$NON-NLS-1$
+ buffer.append(getDepthString());
+ return buffer.toString();
+ }
+ protected String getDepthString() {
+ switch (depth) {
+ case IResource.DEPTH_ZERO :
+ return "DEPTH_ZERO"; //$NON-NLS-1$
+ case IResource.DEPTH_ONE :
+ return "DEPTH_ONE"; //$NON-NLS-1$
+ case IResource.DEPTH_INFINITE :
+ return "DEPTH_INFINITE"; //$NON-NLS-1$
+ default :
+ return "INVALID"; //$NON-NLS-1$
+ }
+ }
+ protected String getTypeString() {
+ return String.valueOf(type);
+ }
+ }
+
+ protected BackgroundEventHandler(String jobName, String errorTitle) {
+ this.jobName = jobName;
+ errors =
+ new ExceptionCollector(
+ errorTitle,
+ TeamPlugin.ID,
+ IStatus.ERROR,
+ null /* don't log */
+ );
+ createEventHandlingJob();
+ schedule();
+ }
+
+ /**
+ * Create the job used for processing the events in the queue. The job stops working when
+ * the queue is empty.
+ */
+ protected void createEventHandlingJob() {
+ eventHandlerJob = new Job(getName()) {
+ public IStatus run(IProgressMonitor monitor) {
+ return processEvents(monitor);
+ }
+ public boolean shouldRun() {
+ return ! isQueueEmpty();
+ }
+ public boolean shouldSchedule() {
+ return ! isQueueEmpty();
+ }
+ };
+ eventHandlerJob.addJobChangeListener(new JobChangeAdapter() {
+ public void done(IJobChangeEvent event) {
+ jobDone(event);
+ }
+ });
+ eventHandlerJob.setSystem(true);
+ eventHandlerJob.setPriority(Job.SHORT);
+ }
+
+ /**
+ * This method is invoked when the processing job completes. The
+ * default behavior of the handler is to restart the job if the queue
+ * is no longer empty and to clear the queue if the handler was shut down.
+ */
+ protected void jobDone(IJobChangeEvent event) {
+ if (isShutdown()) {
+ // The handler has been shutdown. Clean up the queue.
+ synchronized(this) {
+ awaitingProcessing.clear();
+ }
+ } else if (! isQueueEmpty()) {
+ // An event squeaked in as the job was finishing. Reschedule the job.
+ schedule();
+ }
+ }
+
+ /**
+ * Schedule the job to process the events now.
+ */
+ protected void schedule() {
+ eventHandlerJob.schedule();
+ }
+
+ /**
+ * Shutdown the event handler. Any events on the queue will be removed from the queue
+ * and will not be processed.
+ */
+ public void shutdown() {
+ shutdown = true;
+ eventHandlerJob.cancel();
+ }
+
+ /**
+ * Returns whether the handle has been shutdown.
+ * @return Returns whether the handle has been shutdown.
+ */
+ public boolean isShutdown() {
+ return shutdown;
+ }
+
+ /**
+ * Queue the event and start the job if it's not already doing work. If the job is
+ * already running then notify in case it was waiting.
+ * @param event the event to be queued
+ */
+ protected synchronized void queueEvent(Event event, boolean front) {
+ if (Policy.DEBUG_BACKGROUND_EVENTS) {
+ System.out.println("Event queued on " + getName() + ":" + event.toString()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (front) {
+ awaitingProcessing.add(0, event);
+ } else {
+ awaitingProcessing.add(event);
+ }
+ if (!isShutdown() && eventHandlerJob != null) {
+ if(eventHandlerJob.getState() == Job.NONE) {
+ schedule();
+ } else {
+ notify();
+ }
+ }
+ }
+
+ /**
+ * Return the name that is to be associated with the background job.
+ * @return the job name
+ */
+ protected String getName() {
+ return jobName;
+ }
+
+ /*
+ * Return the next event that has been queued, removing it from the queue.
+ * @return the next event in the queue
+ */
+ protected synchronized Event nextElement() {
+ if (isShutdown() || isQueueEmpty()) {
+ return null;
+ }
+ return (Event) awaitingProcessing.remove(0);
+ }
+
+ protected synchronized Event peek() {
+ if (isShutdown() || isQueueEmpty()) {
+ return null;
+ }
+ return (Event) awaitingProcessing.get(0);
+ }
+
+ /**
+ * Return whether there are unprocessed events on the event queue.
+ * @return whether there are unprocessed events on the queue
+ */
+ protected synchronized boolean isQueueEmpty() {
+ return awaitingProcessing.isEmpty();
+ }
+
+ /**
+ * Process events from the events queue and dispatch results. This method does not
+ * directly check for or handle cancelation of the provided monitor. However,
+ * it does invoke <code>processEvent(Event)</code> which may check for and handle
+ * cancelation by shuting down the receiver.
+ * <p>
+ * The <code>isReadyForDispatch()</code> method is used in conjuntion
+ * with the <code>dispatchEvents(IProgressMonitor)</code> to allow
+ * the output of the event handler to be batched in order to avoid
+ * fine grained UI updating.
+ * @param monitor a progress monitor
+ */
+ protected IStatus processEvents(IProgressMonitor monitor) {
+ errors.clear();
+ try {
+ // It's hard to know how much work is going to happen
+ // since the queue can grow. Use the current queue size as a hint to
+ // an infinite progress monitor
+ monitor.beginTask(null, 100);
+ IProgressMonitor subMonitor = Policy.infiniteSubMonitorFor(monitor, 90);
+ subMonitor.beginTask(null, 1024);
+
+ Event event;
+ processingEventsDuration = System.currentTimeMillis();
+ while ((event = nextElement()) != null && ! isShutdown()) {
+ try {
+ processEvent(event, subMonitor);
+ if (Policy.DEBUG_BACKGROUND_EVENTS) {
+ System.out.println("Event processed on " + getName() + ":" + event.toString()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if(isReadyForDispatch(true /*wait if queue is empty*/)) {
+ dispatchEvents(Policy.subMonitorFor(subMonitor, 1));
+ eventsDispatched();
+ }
+ } catch (CoreException e) {
+ // handle exception but keep going
+ handleException(e);
+ }
+ }
+ } finally {
+ monitor.done();
+ }
+ return errors.getStatus();
+ }
+
+ protected void eventsDispatched() {
+ processingEventsDuration = System.currentTimeMillis();
+ }
+
+ /**
+ * Notify clients of processed events.
+ * @param monitor a progress monitor
+ */
+ protected abstract void dispatchEvents(IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * Returns <code>true</code> if processed events should be dispatched and
+ * <code>false</code> otherwise. Events are dispatched at regular intervals
+ * to avoid fine grain events causing the UI to be too jumpy. Also, if the
+ * events queue is empty we will wait a small amount of time to allow
+ * pending events to be queued. The queueEvent notifies when events are
+ * queued.
+ * @return <code>true</code> if processed events should be dispatched and
+ * <code>false</code> otherwise
+ */
+ protected boolean isReadyForDispatch(boolean wait) {
+ long duration = System.currentTimeMillis() - processingEventsDuration;
+ if(duration >= DISPATCH_DELAY) {
+ return true;
+ }
+ synchronized(this) {
+ if(! isQueueEmpty() || ! wait) {
+ return false;
+ }
+ try {
+ wait(WAIT_DELAY);
+ } catch (InterruptedException e) {
+ // just continue
+ }
+ }
+ return isQueueEmpty();
+ }
+
+ /**
+ * Handle the exception by recording it in the errors list.
+ * @param e
+ */
+ protected void handleException(CoreException e) {
+ errors.handleException(e);
+
+ }
+
+ /**
+ * Process the event in the context of a running background job. Subclasses may
+ * (but are not required to) check the provided monitor for cancelation and shut down the
+ * receiver by invoking the <code>shutdown()</code> method.
+ * <p>
+ * In many cases, a background event handler will translate incoming events into outgoing
+ * events. If this is the case, the handler should accumulate these events in the
+ * <code>proceessEvent</code> method and propogate them from the <code>dispatchEvent</code>
+ * method which is invoked periodically in order to batch outgoing events and avoid
+ * the UI becoming too jumpy.
+ *
+ * @param event the <code>Event</code> to be processed
+ * @param monitor a progress monitor
+ */
+ protected abstract void processEvent(Event event, IProgressMonitor monitor) throws CoreException;
+
+ /**
+ * Return the job from which the <code>processedEvent</code> method is invoked.
+ * @return Returns the background event handlig job.
+ */
+ public Job getEventHandlerJob() {
+ return eventHandlerJob;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DefaultFileModificationValidator.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DefaultFileModificationValidator.java
index 82bf0d998..90e5c154a 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DefaultFileModificationValidator.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DefaultFileModificationValidator.java
@@ -10,20 +10,17 @@
*******************************************************************************/
package org.eclipse.team.internal.core;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IFileModificationValidator;
-import org.eclipse.core.resources.IResourceStatus;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.MultiStatus;
-import org.eclipse.core.runtime.Status;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
import org.eclipse.team.core.Team;
public class DefaultFileModificationValidator implements IFileModificationValidator {
private static final Status OK = Team.OK_STATUS;
private IStatus getDefaultStatus(IFile file) {
- return file.isReadOnly()
- ? new Status(Status.ERROR, TeamPlugin.ID, IResourceStatus.READ_ONLY_LOCAL, Policy.bind("FileModificationValidator.fileIsReadOnly", file.getFullPath().toString()), null) //$NON-NLS-1$
+ return
+ file.isReadOnly()
+ ? new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.READ_ONLY_LOCAL, Policy.bind("FileModificationValidator.fileIsReadOnly", file.getFullPath().toString()), null) //$NON-NLS-1$
: OK;
}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DeploymentProviderManager.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DeploymentProviderManager.java
new file mode 100644
index 000000000..018c8a860
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/DeploymentProviderManager.java
@@ -0,0 +1,447 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+import java.io.*;
+import java.util.*;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.team.core.*;
+import org.eclipse.team.internal.core.registry.DeploymentProviderDescriptor;
+import org.eclipse.team.internal.core.registry.DeploymentProviderRegistry;
+import org.eclipse.team.internal.core.Policy;
+
+public class DeploymentProviderManager implements IDeploymentProviderManager, IResourceChangeListener {
+
+ // key for remembering if state has been loaded for a project
+ private final static QualifiedName STATE_LOADED_KEY = new QualifiedName("org.eclipse.team.core.deployment", "state_restored_key"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ // {project -> list of Mapping}
+ private Map mappings = new HashMap(5);
+
+ // registry for deployment provider extensions
+ private DeploymentProviderRegistry registry;
+
+ // lock to ensure that map/unmap and getProvider support concurrency
+ private static final ILock mappingLock = Platform.getJobManager().newLock();
+
+ // persistence constants
+ private final static String CTX_PROVIDERS = "deploymentProviders"; //$NON-NLS-1$
+ private final static String CTX_PROVIDER = "provider"; //$NON-NLS-1$
+ private final static String CTX_ID = "id"; //$NON-NLS-1$
+ private final static String CTX_PATH = "container_path"; //$NON-NLS-1$
+ private final static String CTX_PROVIDER_DATA = "data"; //$NON-NLS-1$
+ private final static String FILENAME = ".deployments"; //$NON-NLS-1$
+
+ static class Mapping {
+ private DeploymentProviderDescriptor descriptor;
+ private DeploymentProvider provider;
+ private IContainer container;
+ private IMemento savedState;
+
+ Mapping(DeploymentProviderDescriptor descriptor, IContainer container) {
+ this.descriptor = descriptor;
+ this.container = container;
+ }
+ public DeploymentProvider getProvider() throws TeamException {
+ if(provider == null) {
+ try {
+ this.provider = descriptor.createProvider();
+ this.provider.setContainer(container);
+ this.provider.restoreState(savedState);
+ this.savedState = null;
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+ return provider;
+ }
+ public void setProvider(DeploymentProvider provider) {
+ this.provider = provider;
+ this.savedState = null;
+ }
+ public IContainer getContainer() {
+ return container;
+ }
+ public DeploymentProviderDescriptor getDescription() {
+ return descriptor;
+ }
+ public void setProviderState(IMemento savedState) {
+ this.savedState = savedState;
+ }
+ }
+
+ public DeploymentProviderManager() {
+ registry = new DeploymentProviderRegistry();
+ }
+
+ public void map(IContainer container, DeploymentProvider deploymentProvider) throws TeamException {
+ try {
+ mappingLock.acquire();
+ if (!deploymentProvider.isMultipleMappingsSupported()) {
+ // don't allow is overlapping deployment providers of the same type
+ checkOverlapping(container, deploymentProvider.getID());
+ }
+
+ // extension point descriptor must exist
+ DeploymentProviderDescriptor descriptor = registry.find(deploymentProvider.getID());
+ if(descriptor == null) {
+ throw new TeamException(Policy.bind("DeploymentProviderManager.10", deploymentProvider.getID())); //$NON-NLS-1$
+ }
+
+ // create the new mapping
+ Mapping m = internalMap(container, descriptor);
+ m.setProvider(deploymentProvider);
+ deploymentProvider.setContainer(container);
+ deploymentProvider.init();
+
+ saveState(container.getProject());
+ // TODO: what kind of event is generated when one is mapped?
+ } finally {
+ mappingLock.release();
+ }
+ }
+
+ public void unmap(IContainer container, DeploymentProvider teamProvider) throws TeamException {
+ try {
+ mappingLock.acquire();
+ IProject project = container.getProject();
+ List projectMaps = internalGetMappings(container);
+ Mapping[] m = internalGetMappingsFor(container, teamProvider.getID());
+ for (int i = 0; i < m.length; i++) {
+ Mapping mapping = m[i];
+ if (mapping.getProvider() == teamProvider) {
+ projectMaps.remove(mapping);
+ if(projectMaps.isEmpty()) {
+ mappings.remove(project);
+ }
+ }
+ }
+
+ // dispose of provider
+ teamProvider.dispose();
+ saveState(container.getProject());
+
+ // TODO: what kind of event is sent when unmapped?
+ } finally {
+ mappingLock.release();
+ }
+ }
+
+ public DeploymentProvider[] getMappings(IResource resource) {
+ List projectMappings = internalGetMappings(resource);
+ String fullPath = resource.getFullPath().toString();
+ List result = new ArrayList();
+ if(projectMappings != null) {
+ for (Iterator it = projectMappings.iterator(); it.hasNext();) {
+ Mapping m = (Mapping) it.next();
+ if(fullPath.startsWith(m.getContainer().getFullPath().toString())) {
+ try {
+ // lazy initialize of provider must be supported
+ // TODO: It is possible that the provider has been unmap concurrently
+ result.add(m.getProvider());
+ } catch (CoreException e) {
+ TeamPlugin.log(e);
+ }
+ }
+ }
+ }
+ return (DeploymentProvider[]) result.toArray(new DeploymentProvider[result.size()]);
+ }
+
+ public DeploymentProvider[] getMappings(IResource resource, String id) {
+ Mapping[] m = internalGetMappingsFor(resource, id);
+ List result = new ArrayList();
+ for (int i = 0; i < m.length; i++) {
+ Mapping mapping = m[i];
+ try {
+ // lazy initialize of provider must be supported
+ // TODO: It is possible that the provider has been unmap concurrently
+ result.add(mapping.getProvider());
+ } catch (TeamException e) {
+ TeamPlugin.log(e);
+ }
+ }
+
+ DeploymentProvider[] providers = (DeploymentProvider[]) result.toArray(new DeploymentProvider[result.size()]);
+ // Ensure that multiple providers are not mapped if it is not supported
+ // by the provider type. This could occur if the deployment configuration
+ // was loaded from a repository or modified manually
+ if (providers.length > 1 && !providers[0].isMultipleMappingsSupported()) {
+ // Log and ignore all but one of the mappings
+ TeamPlugin.log(IStatus.WARNING, Policy.bind("DeploymentProviderManager.12", resource.getFullPath().toString(), id), null); //$NON-NLS-1$
+ return new DeploymentProvider[] { providers[0] };
+ }
+ return providers;
+ }
+
+ public boolean getMappedTo(IResource resource, String id) {
+ return internalGetMappingsFor(resource, id).length > 0;
+ }
+
+ private void checkOverlapping(IContainer container, String id) throws TeamException {
+ List projectMappings = internalGetMappings(container);
+ String fullPath = container.getFullPath().toString();
+ if(projectMappings != null) {
+ for (Iterator it = projectMappings.iterator(); it.hasNext();) {
+ Mapping m = (Mapping) it.next();
+ String first = m.getContainer().getFullPath().toString();
+ if(fullPath.startsWith(first) || first.startsWith(fullPath)) {
+ if (m.getDescription().getId().equals(id)) {
+ throw new TeamException(Policy.bind("DeploymentProviderManager.13", container.getFullPath().toString(), m.getDescription().getId())); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+ }
+
+ private Mapping internalMap(IContainer container, DeploymentProviderDescriptor description) {
+ Mapping newMapping = new Mapping(description, container);
+ return internalMap(container, newMapping);
+ }
+
+ private Mapping internalMap(IContainer container, Mapping newMapping) {
+ IProject project = container.getProject();
+ List projectMaps = (List)mappings.get(project);
+ if(projectMaps == null) {
+ projectMaps = new ArrayList();
+ mappings.put(project, projectMaps);
+ }
+ projectMaps.add(newMapping);
+ return newMapping;
+ }
+
+ /*
+ * Loads all the mappings associated with the resource's project.
+ */
+ private List internalGetMappings(IResource resource) {
+ try {
+ mappingLock.acquire();
+ IProject project = resource.getProject();
+ List m = (List)mappings.get(project);
+ try {
+ if(project.getSessionProperty(STATE_LOADED_KEY) != null) {
+ return m;
+ }
+ Mapping[] projectMappings = loadMappings(project);
+ for (int i = 0; i < projectMappings.length; i++) {
+ Mapping mapping = projectMappings[i];
+ internalMap(mapping.getContainer(), mapping);
+ }
+
+ project.setSessionProperty(STATE_LOADED_KEY, new Object());
+ } catch (TeamException e) {
+ } catch (CoreException e) {
+ }
+ return (List)mappings.get(project);
+ } finally {
+ mappingLock.release();
+ }
+ }
+
+ private Mapping[] internalGetMappingsFor(IResource resource, String id) {
+ List projectMappings = internalGetMappings(resource);
+ List result = new ArrayList();
+ String fullPath = resource.getFullPath().toString();
+ if(projectMappings != null) {
+ for (Iterator it = projectMappings.iterator(); it.hasNext();) {
+ Mapping m = (Mapping) it.next();
+ // mapping can be initialize without having provider loaded yet!
+ if(m.getDescription().getId().equals(id) && fullPath.startsWith(m.getContainer().getFullPath().toString())) {
+ result.add(m);
+ }
+ }
+ }
+ return (Mapping[]) result.toArray(new Mapping[result.size()]);
+ }
+
+ /**
+ * Saves a file containing the list of participant ids that are registered with this
+ * manager. Each participant is also given the chance to save it's state.
+ */
+ private void saveState(IProject project) throws TeamException {
+ File file = getNonsharedSettingsFile(project);
+ try {
+ XMLMemento xmlMemento = XMLMemento.createWriteRoot(CTX_PROVIDERS);
+ List providers = (List)mappings.get(project);
+ if(providers == null) {
+ if (file.exists()) {
+ file.delete();
+ }
+ } else {
+ for (Iterator it2 = providers.iterator(); it2.hasNext(); ) {
+ Mapping mapping = (Mapping) it2.next();
+ IMemento node = xmlMemento.createChild(CTX_PROVIDER);
+ node.putString(CTX_ID, mapping.getDescription().getId());
+ node.putString(CTX_PATH, mapping.getContainer().getProjectRelativePath().toString());
+ mapping.getProvider().saveState(node.createChild(CTX_PROVIDER_DATA));
+ }
+ Writer writer = new BufferedWriter(new FileWriter(file));
+ try {
+ xmlMemento.save(writer);
+ } finally {
+ writer.close();
+ }
+ }
+ } catch (IOException e) {
+ throw new TeamException(Policy.bind("DeploymentProviderManager.15", project.getName()), e); //$NON-NLS-1$
+ } catch(CoreException ce) {
+ throw TeamException.asTeamException(ce);
+ }
+ }
+
+ /**
+ * @param project
+ * @return
+ */
+ private File getNonsharedSettingsFile(IProject project) {
+ IPath metaPath = project.getPluginWorkingLocation(TeamPlugin.getPlugin().getDescriptor());
+ metaPath = metaPath.append(FILENAME);
+ File file = metaPath.toFile();
+ return file;
+ }
+
+ /*
+ * Load the mappings for the given project and return them.
+ */
+ private Mapping[] loadMappings(IProject project) throws TeamException, CoreException {
+ File file = getNonsharedSettingsFile(project);
+ if(! file.exists()) {
+ // The file may have been deleted before our delta listener was loaded.
+ // If there are any deployments stored in the meta data area, dispose of them
+ // TODO: See if there were any before and dispose of them
+ return new Mapping[0];
+ }
+ Reader reader;
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ } catch (FileNotFoundException e) {
+ return new Mapping[0];
+ }
+ return loadMappings(project, reader);
+ }
+
+ private Mapping[] loadMappings(IProject project, Reader reader) throws TeamException {
+ try {
+ IMemento memento = XMLMemento.createReadRoot(reader);
+ IMemento[] providers = memento.getChildren(CTX_PROVIDER);
+ List projectMappings = new ArrayList();
+ for (int i = 0; i < providers.length; i++) {
+ IMemento memento2 = providers[i];
+ String id = memento2.getString(CTX_ID);
+ IPath location = new Path(memento2.getString(CTX_PATH));
+
+ if(! project.exists(location)) {
+ TeamPlugin.log(IStatus.ERROR, Policy.bind("DeploymentProviderManager.16", location.toString(), project.getName()), null); //$NON-NLS-1$
+ }
+ IResource resource = location.isEmpty() ? (IContainer)project : project.findMember(location);
+ if (resource.getType() == IResource.FILE) {
+ TeamPlugin.log(IStatus.ERROR, Policy.bind("DeploymentProviderManager.17", location.toString(), project.getName()), null); //$NON-NLS-1$
+ }
+ IContainer container = (IContainer)resource;
+ DeploymentProviderDescriptor desc = registry.find(id);
+ if(desc != null) {
+ Mapping m = new Mapping(desc, container);
+ m.setProviderState(memento2.getChild(CTX_PROVIDER_DATA));
+ projectMappings.add(m);
+ } else {
+ TeamPlugin.log(IStatus.ERROR, Policy.bind("SynchronizeManager.9", id), null); //$NON-NLS-1$
+ }
+ }
+ return (Mapping[]) projectMappings.toArray(new Mapping[projectMappings.size()]);
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.IDeploymentProviderManager#getDeploymentProviderRoots(java.lang.String)
+ */
+ public IResource[] getDeploymentProviderRoots(String id) {
+ IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+ Set roots = new HashSet();
+ for (int i = 0; i < projects.length; i++) {
+ IProject project = projects[i];
+ List mappings = internalGetMappings(project);
+ if (mappings != null) {
+ for (Iterator iter = mappings.iterator(); iter.hasNext();) {
+ Mapping mapping = (Mapping) iter.next();
+ if (id == null || mapping.getDescription().getId().equals(id)) {
+ roots.add(mapping.getContainer());
+ }
+ }
+ }
+ }
+ return (IResource[]) roots.toArray(new IResource[roots.size()]);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
+ */
+ public void resourceChanged(IResourceChangeEvent event) {
+ processDelta(event.getDelta());
+ }
+
+ private void processDelta(IResourceDelta delta) {
+ IResource resource = delta.getResource();
+ int kind = delta.getKind();
+
+ if (resource.getType() == IResource.PROJECT) {
+ // Handle a deleted project
+ if (((kind & IResourceDelta.REMOVED) != 0)) {
+ handleProjectRemoval((IProject)resource);
+ return;
+ }
+ // Handle a closed project
+ if ((delta.getFlags() & IResourceDelta.OPEN) != 0 && !((IProject) resource).isOpen()) {
+ handleProjectClose((IProject)resource);
+ return;
+ }
+ }
+
+ if (((kind & IResourceDelta.REMOVED) != 0) && (resource.getType() == IResource.FOLDER)) {
+ handleFolderRemoval((IFolder)resource);
+ }
+
+ // Handle changed children
+ IResourceDelta[] affectedChildren = delta.getAffectedChildren(IResourceDelta.CHANGED | IResourceDelta.REMOVED | IResourceDelta.ADDED);
+ for (int i = 0; i < affectedChildren.length; i++) {
+ processDelta(affectedChildren[i]);
+ }
+ }
+
+ private void handleFolderRemoval(IFolder folder) {
+ DeploymentProvider[] providers = getMappings(folder);
+ for (int i = 0; i < providers.length; i++) {
+ DeploymentProvider provider = providers[i];
+ try {
+ unmap(folder, provider);
+ } catch (TeamException e) {
+ TeamPlugin.log(e);
+ }
+ }
+ }
+
+ private void handleProjectClose(IProject project) {
+ mappings.remove(project);
+ }
+
+ private void handleProjectRemoval(IProject project) {
+ mappings.remove(project);
+ }
+
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IJobListener.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IJobListener.java
new file mode 100644
index 000000000..3962d7f1d
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IJobListener.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+import org.eclipse.core.runtime.QualifiedName;
+
+/**
+ * This interface allows interested parties to receive notification
+ * when work has started or stopped for a given job type. The <code>started</code>
+ * method is invoked when the first job is started for the given <code>jobType</code>.
+ * The <code>finish</code> method is called when the last job of a given type stops.
+ * Several jobs for the job type may start and stop in the interum without causing
+ * notification to the listener.
+ */
+public interface IJobListener {
+ public void started(QualifiedName jobType);
+ public void finished(QualifiedName jobType);
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IMemento.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IMemento.java
new file mode 100644
index 000000000..fff33a1b7
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/IMemento.java
@@ -0,0 +1,169 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+/**
+ * Interface to a memento used for saving the important state of an object
+ * in a form that can be persisted in the file system.
+ * <p>
+ * Mementos were designed with the following requirements in mind:
+ * <ol>
+ * <li>Certain objects need to be saved and restored across platform sessions.
+ * </li>
+ * <li>When an object is restored, an appropriate class for an object might not
+ * be available. It must be possible to skip an object in this case.</li>
+ * <li>When an object is restored, the appropriate class for the object may be
+ * different from the one when the object was originally saved. If so, the
+ * new class should still be able to read the old form of the data.</li>
+ * </ol>
+ * </p>
+ * <p>
+ * Mementos meet these requirements by providing support for storing a
+ * mapping of arbitrary string keys to primitive values, and by allowing
+ * mementos to have other mementos as children (arranged into a tree).
+ * A robust external storage format based on XML is used.
+ * </p><p>
+ * The key for an attribute may be any alpha numeric value. However, the
+ * value of <code>TAG_ID</code> is reserved for internal use.
+ * </p><p>
+ * This interface is not intended to be implemented or extended by clients.
+ * </p>
+ *
+ * @see IPersistableElement
+ * @see IElementFactory
+ */
+public interface IMemento {
+ /**
+ * Special reserved key used to store the memento id
+ * (value <code>"org.eclipse.ui.id"</code>).
+ *
+ * @see #getID()
+ */
+ public static final String TAG_ID = "IMemento.internal.id"; //$NON-NLS-1$
+ /**
+ * Creates a new child of this memento with the given type.
+ * <p>
+ * The <code>getChild</code> and <code>getChildren</code> methods
+ * are used to retrieve children of a given type.
+ * </p>
+ *
+ * @param type the type
+ * @return a new child memento
+ * @see #getChild
+ * @see #getChildren
+ */
+ public IMemento createChild(String type);
+ /**
+ * Creates a new child of this memento with the given type and id.
+ * The id is stored in the child memento (using a special reserved
+ * key, <code>TAG_ID</code>) and can be retrieved using <code>getId</code>.
+ * <p>
+ * The <code>getChild</code> and <code>getChildren</code> methods
+ * are used to retrieve children of a given type.
+ * </p>
+ *
+ * @param type the type
+ * @param id the child id
+ * @return a new child memento with the given type and id
+ * @see #getID
+ */
+ public IMemento createChild(String type, String id);
+ /**
+ * Returns the first child with the given type id.
+ *
+ * @param type the type id
+ * @return the first child with the given type
+ */
+ public IMemento getChild(String type);
+ /**
+ * Returns all children with the given type id.
+ *
+ * @param type the type id
+ * @return the list of children with the given type
+ */
+ public IMemento[] getChildren(String type);
+ /**
+ * Returns the floating point value of the given key.
+ *
+ * @param key the key
+ * @return the value, or <code>null</code> if the key was not found or was found
+ * but was not a floating point number
+ */
+ public Float getFloat(String key);
+ /**
+ * Returns the id for this memento.
+ *
+ * @return the memento id, or <code>null</code> if none
+ * @see #createChild(java.lang.String,java.lang.String)
+ */
+ public String getID();
+ /**
+ * Returns the integer value of the given key.
+ *
+ * @param key the key
+ * @return the value, or <code>null</code> if the key was not found or was found
+ * but was not an integer
+ */
+ public Integer getInteger(String key);
+ /**
+ * Returns the string value of the given key.
+ *
+ * @param key the key
+ * @return the value, or <code>null</code> if the key was not found
+ */
+ public String getString(String key);
+ /**
+ * Returns the data of the Text node of the memento. Each memento is allowed
+ * only one Text node.
+ *
+ * @return the data of the Text node of the memento, or <code>null</code>
+ * if the memento has no Text node.
+ * @since 2.0
+ */
+ public String getTextData();
+ /**
+ * Sets the value of the given key to the given floating point number.
+ *
+ * @param key the key
+ * @param value the value
+ */
+ public void putFloat(String key, float value);
+ /**
+ * Sets the value of the given key to the given integer.
+ *
+ * @param key the key
+ * @param value the value
+ */
+ public void putInteger(String key, int value);
+ /**
+ * Copy the attributes and children from <code>memento</code>
+ * to the receiver.
+ *
+ * @param memento the IMemento to be copied.
+ */
+ public void putMemento(IMemento memento);
+ /**
+ * Sets the value of the given key to the given string.
+ *
+ * @param key the key
+ * @param value the value
+ */
+ public void putString(String key, String value);
+ /**
+ * Sets the memento's Text node to contain the given data. Creates the Text node if
+ * none exists. If a Text node does exist, it's current contents are replaced.
+ * Each memento is allowed only one text node.
+ *
+ * @param data the data to be placed on the Text node
+ * @since 2.0
+ */
+ public void putTextData(String data);
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java
new file mode 100644
index 000000000..cda7872e2
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCache.java
@@ -0,0 +1,250 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.CachedResourceVariant;
+
+/**
+ * This class implements a caching facility that can be used by TeamProviders to cache contents
+ */
+public class ResourceVariantCache {
+
+ // Directory to cache file contents
+ private static final String CACHE_DIRECTORY = ".cache"; //$NON-NLS-1$
+ // Maximum lifespan of local cache file, in milliseconds
+ private static final long CACHE_FILE_LIFESPAN = 60*60*1000; // 1hr
+
+ // Map of registered cahces indexed by local name of a QualifiedName
+ private static Map caches = new HashMap(); // String (local name) > RemoteContentsCache
+
+ private String name;
+ private Map cacheEntries;
+ private long lastCacheCleanup;
+ private int cacheDirSize;
+
+ // Lock used to serialize the writting of cache contents
+ private ILock lock = Platform.getJobManager().newLock();
+
+ /**
+ * Enables the use of remote contents caching for the given cacheId. The cache ID must be unique.
+ * A good candidate for this ID is the plugin ID of the plugin peforming the caching.
+ *
+ * @param cacheId the unique Id of the cache being enabled
+ * @throws TeamException if the cache area on disk could not be properly initialized
+ */
+ public static synchronized void enableCaching(String cacheId) {
+ if (isCachingEnabled(cacheId)) return;
+ ResourceVariantCache cache = new ResourceVariantCache(cacheId);
+ try {
+ cache.createCacheDirectory();
+ } catch (TeamException e) {
+ // Log the exception and continue
+ TeamPlugin.log(e);
+ }
+ caches.put(cacheId, cache);
+ }
+
+ /**
+ * Returns whether caching has been enabled for the given Id. A cache should only be enabled once.
+ * It is conceivable that a cache be persisted over workbench invocations thus leading to a cahce that
+ * is enabled on startup without intervention by the owning plugin.
+ *
+ * @param cacheId the unique Id of the cache
+ * @return true if caching for the given Id is enabled
+ */
+ public static boolean isCachingEnabled(String cacheId) {
+ return getCache(cacheId) != null;
+ }
+
+ /**
+ * Disable the cache, dispoing of any file contents in the cache.
+ *
+ * @param cacheId the unique Id of the cache
+ * @throws TeamException if the cached contents could not be deleted from disk
+ */
+ public static void disableCache(String cacheId) {
+ ResourceVariantCache cache = getCache(cacheId);
+ if (cache == null) {
+ // There is no cache to dispose of
+ return;
+ }
+ caches.remove(cacheId);
+ try {
+ cache.deleteCacheDirectory();
+ } catch (TeamException e) {
+ // Log the exception and continue
+ TeamPlugin.log(e);
+ }
+ }
+
+ /**
+ * Return the cache for the given id or null if caching is not enabled for the given id.
+ * @param cacheId
+ * @return
+ */
+ public static synchronized ResourceVariantCache getCache(String cacheId) {
+ return (ResourceVariantCache)caches.get(cacheId);
+ }
+
+ public static synchronized void shutdown() {
+ for (Iterator iter = caches.keySet().iterator(); iter.hasNext();) {
+ String id = (String) iter.next();
+ disableCache(id);
+ }
+ }
+
+ private ResourceVariantCache(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Return whether the cache contains an entry for the given id. Register a hit if it does.
+ * @param id the id of the cache entry
+ * @return true if there are contents cached for the id
+ */
+ public boolean hasEntry(String id) {
+ return internalGetCacheEntry(id) != null;
+ }
+
+ protected IPath getCachePath() {
+ return getStateLocation().append(CACHE_DIRECTORY).append(name);
+ }
+
+ private IPath getStateLocation() {
+ return TeamPlugin.getPlugin().getStateLocation();
+ }
+
+ private synchronized void clearOldCacheEntries() {
+ long current = new Date().getTime();
+ if ((lastCacheCleanup!=-1) && (current - lastCacheCleanup < CACHE_FILE_LIFESPAN)) return;
+ List stale = new ArrayList();
+ for (Iterator iter = cacheEntries.values().iterator(); iter.hasNext();) {
+ ResourceVariantCacheEntry entry = (ResourceVariantCacheEntry) iter.next();
+ long lastHit = entry.getLastAccessTimeStamp();
+ if ((current - lastHit) > CACHE_FILE_LIFESPAN){
+ stale.add(entry);
+ }
+ }
+ for (Iterator iter = stale.iterator(); iter.hasNext();) {
+ ResourceVariantCacheEntry entry = (ResourceVariantCacheEntry) iter.next();
+ entry.dispose();
+ }
+ }
+
+ private synchronized void purgeFromCache(String id) {
+ ResourceVariantCacheEntry entry = (ResourceVariantCacheEntry)cacheEntries.get(id);
+ File f = entry.getFile();
+ try {
+ deleteFile(f);
+ } catch (TeamException e) {
+ // Ignore the deletion failure.
+ // A failure only really matters when purging the directory on startup
+ }
+ cacheEntries.remove(id);
+ }
+
+ private synchronized void createCacheDirectory() throws TeamException {
+ IPath cacheLocation = getCachePath();
+ File file = cacheLocation.toFile();
+ if (file.exists()) {
+ deleteFile(file);
+ }
+ if (! file.mkdirs()) {
+ throw new TeamException(Policy.bind("RemoteContentsCache.fileError", file.getAbsolutePath())); //$NON-NLS-1$
+ }
+ cacheEntries = new HashMap();
+ lastCacheCleanup = -1;
+ cacheDirSize = 0;
+ }
+
+ private synchronized void deleteCacheDirectory() throws TeamException {
+ cacheEntries = null;
+ lastCacheCleanup = -1;
+ cacheDirSize = 0;
+ IPath cacheLocation = getCachePath();
+ File file = cacheLocation.toFile();
+ if (file.exists()) {
+ try {
+ deleteFile(file);
+ } catch (TeamException e) {
+ // Don't worry about problems deleting.
+ // The only case that matters is when the cache directory is created
+ }
+ }
+ }
+
+ private void deleteFile(File file) throws TeamException {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ for (int i = 0; i < children.length; i++) {
+ deleteFile(children[i]);
+ }
+ }
+ if (! file.delete()) {
+ throw new TeamException(Policy.bind("RemoteContentsCache.fileError", file.getAbsolutePath())); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Purge the given cache entry from the cache. This method should only be invoked from
+ * an instance of ResourceVariantCacheEntry after it has set it's state to DISPOSED.
+ * @param entry
+ */
+ protected void purgeFromCache(ResourceVariantCacheEntry entry) {
+ purgeFromCache(entry.getId());
+ }
+
+ private synchronized ResourceVariantCacheEntry internalGetCacheEntry(String id) {
+ if (cacheEntries == null) {
+ // This probably means that the cache has been disposed
+ throw new IllegalStateException(Policy.bind("RemoteContentsCache.cacheDisposed", name)); //$NON-NLS-1$
+ }
+ ResourceVariantCacheEntry entry = (ResourceVariantCacheEntry)cacheEntries.get(id);
+ if (entry != null) {
+ entry.registerHit();
+ }
+ return entry;
+ }
+
+ /**
+ * @param id the id that uniquely identifes the remote resource that is cached.
+ * @return
+ */
+ public ResourceVariantCacheEntry getCacheEntry(String id) {
+ return internalGetCacheEntry(id);
+ }
+
+ public synchronized ResourceVariantCacheEntry add(String id, CachedResourceVariant resource) {
+ clearOldCacheEntries();
+ String filePath = String.valueOf(cacheDirSize++);
+ ResourceVariantCacheEntry entry = new ResourceVariantCacheEntry(this, lock, id, filePath);
+ entry.setResourceVariant(resource);
+ cacheEntries.put(id, entry);
+ return entry;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCacheEntry.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCacheEntry.java
new file mode 100644
index 000000000..860028151
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/ResourceVariantCacheEntry.java
@@ -0,0 +1,217 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+import java.io.*;
+import java.util.Date;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.CachedResourceVariant;
+
+/**
+ * This class provides the implementation for the ICacheEntry
+ */
+public class ResourceVariantCacheEntry {
+
+ public static final int UNINITIALIZED = 0;
+ public static final int READY = 1;
+ public static final int DISPOSED = 2;
+
+ private String id;
+ private String filePath;
+ private ResourceVariantCache cache;
+ private byte[] syncBytes;
+ private int state = UNINITIALIZED;
+ private long lastAccess;
+ private CachedResourceVariant resourceVariant;
+ private ILock lock;
+
+ public ResourceVariantCacheEntry(ResourceVariantCache cache, ILock lock, String id, String filePath) {
+ this.lock = lock;
+ state = UNINITIALIZED;
+ this.cache = cache;
+ this.id = id;
+ this.filePath = filePath;
+ registerHit();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ICacheEntry#getContents()
+ */
+ public InputStream getContents() throws TeamException {
+ if (state != READY) return null;
+ registerHit();
+ File ioFile = getFile();
+ try {
+ try {
+ if (ioFile.exists()) {
+ return new FileInputStream(ioFile);
+ }
+ } catch (IOException e) {
+ // Try to purge the cache and continue
+ cache.purgeFromCache(this);
+ throw e;
+ }
+ } catch (IOException e) {
+ // We will end up here if we couldn't read or delete the cache file
+ throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$
+ }
+ // This can occur when there is no remote contents
+ return new ByteArrayInputStream(new byte[0]);
+ }
+
+ protected File getFile() {
+ return new File(cache.getCachePath().toFile(), filePath);
+ }
+
+ /**
+ * Set the contents of for this cache entry. This method supports concurrency by only allowing
+ * one cache entry to be written at a time. In the case of two concurrent writes to the same cache entry,
+ * the contents from the first write is used and the content from subsequent writes is ignored.
+ * @param stream an InputStream that provides the contents to be cached
+ * @param monitor a progress monitor
+ * @throws TeamException if the entry is DISPOSED or an I/O error occurres
+ */
+ public void setContents(InputStream stream, IProgressMonitor monitor) throws TeamException {
+ // Use a lock to only allow one write at a time
+ beginOperation();
+ try {
+ internalSetContents(stream, monitor);
+ } finally {
+ endOperation();
+ }
+ }
+
+ private synchronized void endOperation() {
+ lock.release();
+ }
+
+ private synchronized void beginOperation() {
+ lock.acquire();
+ }
+
+ private void internalSetContents(InputStream stream, IProgressMonitor monitor) throws TeamException {
+ // if the state is DISPOSED then there is a problem
+ if (state == DISPOSED) {
+ throw new TeamException(Policy.bind("RemoteContentsCacheEntry.3", cache.getName(), id)); //$NON-NLS-1$
+ }
+ // Otherwise, the state is UNINITIALIZED or READY so we can proceed
+ registerHit();
+ File ioFile = getFile();
+ try {
+
+ // Open the cache file for writing
+ OutputStream out;
+ try {
+ if (state == UNINITIALIZED) {
+ out = new BufferedOutputStream(new FileOutputStream(ioFile));
+ } else {
+ // If the entry is READY, the contents must have been read in another thread.
+ // We still need to red the contents but they can be ignored since presumably they are the same
+ out = new ByteArrayOutputStream();
+ }
+ } catch (FileNotFoundException e) {
+ throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$
+ }
+
+ // Transfer the contents
+ try {
+ try {
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = stream.read(buffer)) >= 0) {
+ Policy.checkCanceled(monitor);
+ out.write(buffer, 0, read);
+ }
+ } finally {
+ out.close();
+ }
+ } catch (IOException e) {
+ // Make sure we don't leave the cache file around as it may not have the right contents
+ cache.purgeFromCache(this);
+ throw e;
+ }
+
+ // Mark the cache entry as ready
+ state = READY;
+ } catch (IOException e) {
+ throw new TeamException(Policy.bind("RemoteContentsCache.fileError", ioFile.getAbsolutePath()), e); //$NON-NLS-1$
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException e1) {
+ // Ignore close errors
+ }
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ICacheEntry#getState()
+ */
+ public int getState() {
+ return state;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ICacheEntry#getSize()
+ */
+ public long getSize() {
+ if (state != READY) return 0;
+ File ioFile = getFile();
+ if (ioFile.exists()) {
+ return ioFile.length();
+ }
+ return 0;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.sync.ICacheEntry#getLastAccessTimeStamp()
+ */
+ public long getLastAccessTimeStamp() {
+ return lastAccess;
+ }
+
+ /**
+ * Registers a hit on this cache entry. This updates the last access timestamp.
+ * Thsi method is intended to only be invokded from inside this class or the cahce itself.
+ * Other clients should not use it.
+ */
+ protected void registerHit() {
+ lastAccess = new Date().getTime();
+ }
+
+ public void dispose() {
+ // Use a lock to avoid changing state while another thread may be writting
+ beginOperation();
+ try {
+ state = DISPOSED;
+ cache.purgeFromCache(this);
+ } finally {
+ endOperation();
+ }
+ }
+
+
+ public String getId() {
+ return id;
+ }
+
+ public CachedResourceVariant getResourceVariant() {
+ return resourceVariant;
+ }
+
+ public void setResourceVariant(CachedResourceVariant resourceVariant) {
+ this.resourceVariant = resourceVariant;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/Sorter.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/Sorter.java
new file mode 100644
index 000000000..475370c94
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/Sorter.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+/**
+ * The SortOperation takes a collection of objects and returns
+ * a sorted collection of these objects. Concrete instances of this
+ * class provide the criteria for the sorting of the objects based on
+ * the type of the objects.
+ */
+public abstract class Sorter {
+ /**
+ * Returns true is elementTwo is 'greater than' elementOne
+ * This is the 'ordering' method of the sort operation.
+ * Each subclass overides this method with the particular
+ * implementation of the 'greater than' concept for the
+ * objects being sorted.
+ */
+ public abstract boolean compare(Object elementOne, Object elementTwo);
+ /**
+ * Sort the objects in sorted collection and return that collection.
+ */
+ private Object[] quickSort(Object[] sortedCollection, int left, int right) {
+ int originalLeft = left;
+ int originalRight = right;
+ Object mid = sortedCollection[ (left + right) / 2];
+ do {
+ while (compare(sortedCollection[left], mid))
+ left++;
+ while (compare(mid, sortedCollection[right]))
+ right--;
+ if (left <= right) {
+ Object tmp = sortedCollection[left];
+ sortedCollection[left] = sortedCollection[right];
+ sortedCollection[right] = tmp;
+ left++;
+ right--;
+ }
+ } while (left <= right);
+ if (originalLeft < right)
+ sortedCollection = quickSort(sortedCollection, originalLeft, right);
+ if (left < originalRight)
+ sortedCollection = quickSort(sortedCollection, left, originalRight);
+ return sortedCollection;
+ }
+ /**
+ * Return a new sorted collection from this unsorted collection.
+ * Sort using quick sort.
+ */
+ public Object[] sort(Object[] unSortedCollection) {
+ int size = unSortedCollection.length;
+ Object[] sortedCollection = new Object[size];
+ //copy the array so can return a new sorted collection
+ System.arraycopy(unSortedCollection, 0, sortedCollection, 0, size);
+ if (size > 1)
+ quickSort(sortedCollection, 0, size - 1);
+ return sortedCollection;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/TeamPlugin.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/TeamPlugin.java
index 5b5d9140f..d646167ff 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/TeamPlugin.java
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/TeamPlugin.java
@@ -71,6 +71,7 @@ final public class TeamPlugin extends Plugin {
*/
public void shutdown() {
Team.shutdown();
+ ResourceVariantCache.shutdown();
}
/**
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/XMLMemento.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/XMLMemento.java
new file mode 100644
index 000000000..17a0bd383
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/XMLMemento.java
@@ -0,0 +1,406 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core;
+
+import java.io.*;
+import java.util.ArrayList;
+
+import javax.xml.parsers.*;
+import javax.xml.transform.*;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.team.core.TeamException;
+import org.w3c.dom.*;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * This class represents the default implementation of the
+ * <code>IMemento</code> interface.
+ * <p>
+ * This class is not intended to be extended by clients.
+ * </p>
+ * [Note: This class has been copied from org.eclipse.ui to get a quick and
+ * dirty xml input/output code. This should be purged once the settings
+ * work is complete]
+ * @see IMemento
+ */
+public final class XMLMemento implements IMemento {
+ private Document factory;
+ private Element element;
+
+ /**
+ * Creates a <code>Document</code> from the <code>Reader</code>
+ * and returns a memento on the first <code>Element</code> for reading
+ * the document.
+ * <p>
+ * Same as calling createReadRoot(reader, null)
+ * </p>
+ *
+ * @param reader the <code>Reader</code> used to create the memento's document
+ * @return a memento on the first <code>Element</code> for reading the document
+ * @throws <code>WorkbenchException</code> if IO problems, invalid format, or no element.
+ */
+ public static XMLMemento createReadRoot(Reader reader) throws TeamException {
+ return createReadRoot(reader, null);
+ }
+
+ /**
+ * Creates a <code>Document</code> from the <code>Reader</code>
+ * and returns a memento on the first <code>Element</code> for reading
+ * the document.
+ *
+ * @param reader the <code>Reader</code> used to create the memento's document
+ * @param baseDir the directory used to resolve relative file names
+ * in the XML document. This directory must exist and include the
+ * trailing separator. The directory format, including the separators,
+ * must be valid for the platform. Can be <code>null</code> if not
+ * needed.
+ * @return a memento on the first <code>Element</code> for reading the document
+ * @throws <code>WorkbenchException</code> if IO problems, invalid format, or no element.
+ */
+ public static XMLMemento createReadRoot(Reader reader, String baseDir) throws TeamException {
+ String messageKey = "XMLMemento.noElement"; //$NON-NLS-1$
+ Exception exception = null;
+
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder parser = factory.newDocumentBuilder();
+ InputSource source = new InputSource(reader);
+ if (baseDir != null)
+ source.setSystemId(baseDir);
+ Document document = parser.parse(source);
+ NodeList list = document.getChildNodes();
+ for (int i = 0; i < list.getLength(); i++) {
+ Node node = list.item(i);
+ if (node instanceof Element)
+ return new XMLMemento(document, (Element) node);
+ }
+ } catch (ParserConfigurationException e) {
+ exception = e;
+ messageKey = "XMLMemento.parserConfigError"; //$NON-NLS-1$
+ } catch (IOException e) {
+ exception = e;
+ messageKey = "XMLMemento.ioError"; //$NON-NLS-1$
+ } catch (SAXException e) {
+ exception = e;
+ messageKey = "XMLMemento.formatError"; //$NON-NLS-1$
+ }
+
+ String problemText = null;
+ if (exception != null)
+ problemText = exception.getMessage();
+ //if (problemText == null || problemText.length() == 0)
+ // problemText = TeamException.getString(messageKey);
+ throw new TeamException(problemText);
+ }
+
+ /**
+ * Returns a root memento for writing a document.
+ *
+ * @param type the element node type to create on the document
+ * @return the root memento for writing a document
+ */
+ public static XMLMemento createWriteRoot(String type) {
+ Document document;
+ try {
+ document = DocumentBuilderFactory
+ .newInstance()
+ .newDocumentBuilder()
+ .newDocument();
+ Element element = document.createElement(type);
+ document.appendChild(element);
+ return new XMLMemento(document, element);
+ }
+ catch (ParserConfigurationException e) {
+ throw new Error(e);
+ }
+ }
+
+ /**
+ * Creates a memento for the specified document and element.
+ * <p>
+ * Clients should use <code>createReadRoot</code> and
+ * <code>createWriteRoot</code> to create the initial
+ * memento on a document.
+ * </p>
+ *
+ * @param document the document for the memento
+ * @param element the element node for the memento
+ */
+ public XMLMemento(Document document, Element element) {
+ super();
+ this.factory = document;
+ this.element = element;
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public IMemento createChild(String type) {
+ Element child = factory.createElement(type);
+ element.appendChild(child);
+ return new XMLMemento(factory, child);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public IMemento createChild(String type, String id) {
+ Element child = factory.createElement(type);
+ child.setAttribute(TAG_ID, id == null ? "" : id); //$NON-NLS-1$
+ element.appendChild(child);
+ return new XMLMemento(factory, child);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public IMemento copyChild(IMemento child) {
+ Element childElement = ((XMLMemento) child).element;
+ Element newElement = (Element) factory.importNode(childElement, true);
+ element.appendChild(newElement);
+ return new XMLMemento(factory, newElement);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public IMemento getChild(String type) {
+
+ // Get the nodes.
+ NodeList nodes = element.getChildNodes();
+ int size = nodes.getLength();
+ if (size == 0)
+ return null;
+
+ // Find the first node which is a child of this node.
+ for (int nX = 0; nX < size; nX++) {
+ Node node = nodes.item(nX);
+ if (node instanceof Element) {
+ Element element = (Element) node;
+ if (element.getNodeName().equals(type))
+ return new XMLMemento(factory, element);
+ }
+ }
+
+ // A child was not found.
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public IMemento[] getChildren(String type) {
+
+ // Get the nodes.
+ NodeList nodes = element.getChildNodes();
+ int size = nodes.getLength();
+ if (size == 0)
+ return new IMemento[0];
+
+ // Extract each node with given type.
+ ArrayList list = new ArrayList(size);
+ for (int nX = 0; nX < size; nX++) {
+ Node node = nodes.item(nX);
+ if (node instanceof Element) {
+ Element element = (Element) node;
+ if (element.getNodeName().equals(type))
+ list.add(element);
+ }
+ }
+
+ // Create a memento for each node.
+ size = list.size();
+ IMemento[] results = new IMemento[size];
+ for (int x = 0; x < size; x++) {
+ results[x] = new XMLMemento(factory, (Element) list.get(x));
+ }
+ return results;
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public Float getFloat(String key) {
+ Attr attr = element.getAttributeNode(key);
+ if (attr == null)
+ return null;
+ String strValue = attr.getValue();
+ try {
+ return new Float(strValue);
+ } catch (NumberFormatException e) {
+ TeamPlugin.log(IStatus.ERROR, "Memento problem - Invalid float for key: " //$NON-NLS-1$
+ + key + " value: " + strValue, null); //$NON-NLS-1$
+ return null;
+ }
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public String getID() {
+ return element.getAttribute(TAG_ID);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public Integer getInteger(String key) {
+ Attr attr = element.getAttributeNode(key);
+ if (attr == null)
+ return null;
+ String strValue = attr.getValue();
+ try {
+ return new Integer(strValue);
+ } catch (NumberFormatException e) {
+ TeamPlugin.log(IStatus.ERROR, "Memento problem - invalid integer for key: " + key //$NON-NLS-1$
+ + " value: " + strValue, null); //$NON-NLS-1$
+ return null;
+ }
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public String getString(String key) {
+ Attr attr = element.getAttributeNode(key);
+ if (attr == null)
+ return null;
+ return attr.getValue();
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public String getTextData() {
+ Text textNode = getTextNode();
+ if (textNode != null) {
+ return textNode.getData();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the Text node of the memento. Each memento is allowed only
+ * one Text node.
+ *
+ * @return the Text node of the memento, or <code>null</code> if
+ * the memento has no Text node.
+ */
+ private Text getTextNode() {
+ // Get the nodes.
+ NodeList nodes = element.getChildNodes();
+ int size = nodes.getLength();
+ if (size == 0)
+ return null;
+ for (int nX = 0; nX < size; nX++) {
+ Node node = nodes.item(nX);
+ if (node instanceof Text) {
+ return (Text) node;
+ }
+ }
+ // a Text node was not found
+ return null;
+ }
+
+ /**
+ * Places the element's attributes into the document.
+ */
+ private void putElement(Element element) {
+ NamedNodeMap nodeMap = element.getAttributes();
+ int size = nodeMap.getLength();
+ for (int i = 0; i < size; i++) {
+ Attr attr = (Attr) nodeMap.item(i);
+ putString(attr.getName(), attr.getValue());
+ }
+
+ NodeList nodes = element.getChildNodes();
+ size = nodes.getLength();
+ for (int i = 0; i < size; i++) {
+ Node node = nodes.item(i);
+ if (node instanceof Element) {
+ XMLMemento child = (XMLMemento) createChild(node.getNodeName());
+ child.putElement((Element) node);
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public void putFloat(String key, float f) {
+ element.setAttribute(key, String.valueOf(f));
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public void putInteger(String key, int n) {
+ element.setAttribute(key, String.valueOf(n));
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public void putMemento(IMemento memento) {
+ putElement(((XMLMemento) memento).element);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public void putString(String key, String value) {
+ if (value == null)
+ return;
+ element.setAttribute(key, value);
+ }
+
+ /* (non-Javadoc)
+ * Method declared in IMemento.
+ */
+ public void putTextData(String data) {
+ Text textNode = getTextNode();
+ if (textNode == null) {
+ textNode = factory.createTextNode(data);
+ element.appendChild(textNode);
+ } else {
+ textNode.setData(data);
+ }
+ }
+
+ /**
+ * Saves this memento's document current values to the
+ * specified writer.
+ *
+ * @param writer the writer used to save the memento's document
+ * @throws IOException if there is a problem serializing the document to the stream.
+ */
+ public void save(Writer writer) throws IOException {
+ Result result = new StreamResult(writer);
+ Source source = new DOMSource(factory);
+ try {
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
+ transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
+ transformer.transform(source, result);
+ }
+ catch (TransformerConfigurationException e) {
+ throw (IOException) (new IOException().initCause(e));
+ }
+ catch (TransformerException e) {
+ throw (IOException) (new IOException().initCause(e));
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/messages.properties b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/messages.properties
index 4dc695713..e55b417f1 100644
--- a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/messages.properties
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/messages.properties
@@ -105,3 +105,21 @@ TeamProvider.10=Error restoring subscribers. Cannot find factory with id: {0}
TeamProvider.11=Error saving subscribers. Cannot find factory with id: {0}
ContentComparisonCriteria.2=Comparing content {0}
ContentComparisonCriteria.3=\ ignoring whitespace
+
+SubscriberEventHandler.2=Calculating synchronization state for {0}.
+SubscriberEventHandler.jobName=Updating synchronization states for {0}.
+SubscriberEventHandler.errors=Errors have occured while calculating the synchronization state for {0}.
+RemoteContentsCacheEntry.3=Cache entry in {0} for {1} has been disposed
+DeploymentProviderManager.10=Cannot map provider {0}. It's extension point description cannot be found.
+DeploymentProviderManager.12=Resource {0} is mapped to multiple deployment providers of type {1}.
+DeploymentProviderManager.13={0} is already mapped to {1}
+DeploymentProviderManager.15=An I/O error occurred while persisting the deployment configurations for project {0}.
+DeploymentProviderManager.16=Previously deployed folder {0} in project {1} no longer exists.
+DeploymentProviderManager.17=Previously deployed resource {0} in project {1} is now a file and cannot be deployed.
+SynchronizationCacheRefreshOperation.0=Refreshing {0}
+SubscriberEventHandler.8=The members of folder {0} could not be retrieved: {1}
+SubscriberEventHandler.9=The synchronization state for resource {0} could not be determined: {1}
+SubscriberEventHandler.10=An internal error occurred processing subscriber events.
+SubscriberEventHandler.11=An internal error occurred processing resource {0}: {1}
+CachedResourceVariant.0=There is no cached contents for resource {0}.
+SyncInfoTree.0=Sync info is missing for resource {0}.
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderDescriptor.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderDescriptor.java
new file mode 100644
index 000000000..980c73871
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderDescriptor.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.registry;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.core.DeploymentProvider;
+
+public class DeploymentProviderDescriptor {
+
+ public static final String ATT_ID = "id"; //$NON-NLS-1$
+ public static final String ATT_NAME = "name"; //$NON-NLS-1$
+ public static final String ATT_CLASS = "class"; //$NON-NLS-1$
+
+ private String name;
+ private String className;
+ private String id;
+ private String description;
+
+ private IConfigurationElement configElement;
+
+ /**
+ * Create a new ViewDescriptor for an extension.
+ */
+ public DeploymentProviderDescriptor(IConfigurationElement e, String desc) throws CoreException {
+ configElement = e;
+ description = desc;
+ loadFromExtension();
+ }
+
+
+ public IConfigurationElement getConfigurationElement() {
+ return configElement;
+ }
+
+ public DeploymentProvider createProvider() throws CoreException {
+ return (DeploymentProvider)configElement.createExecutableExtension(ATT_CLASS);
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ private void loadFromExtension() throws CoreException {
+ String identifier = configElement.getAttribute(ATT_ID);
+ name = configElement.getAttribute(ATT_NAME);
+ className = configElement.getAttribute(ATT_CLASS);
+
+ // Sanity check.
+ if ((name == null) || (className == null) || (identifier == null)) {
+ throw new CoreException(new Status(IStatus.ERROR, configElement.getDeclaringExtension().getDeclaringPluginDescriptor().getUniqueIdentifier(), 0, "Invalid extension (missing label or class name): " + id, //$NON-NLS-1$
+ null));
+ }
+
+ id = identifier;
+ }
+
+ /**
+ * Returns a string representation of this descriptor. For debugging
+ * purposes only.
+ */
+ public String toString() {
+ return "Team Provider(" + getId() + ")"; //$NON-NLS-2$ //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderRegistry.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderRegistry.java
new file mode 100644
index 000000000..09beff082
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/DeploymentProviderRegistry.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.registry;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.internal.core.TeamPlugin;
+
+public class DeploymentProviderRegistry extends RegistryReader {
+
+ private final static String PT_TEAMPROVIDER = "deployment"; //$NON-NLS-1$
+ private Map providers = new HashMap();
+ private String extensionId;
+
+ public DeploymentProviderRegistry() {
+ super();
+ this.extensionId = PT_TEAMPROVIDER;
+ readRegistry(Platform.getPluginRegistry(), TeamPlugin.ID, PT_TEAMPROVIDER);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.registry.RegistryReader#readElement(org.eclipse.core.runtime.IConfigurationElement)
+ */
+ protected boolean readElement(IConfigurationElement element) {
+ if (element.getName().equals(extensionId)) {
+ String descText = getDescription(element);
+ DeploymentProviderDescriptor desc;
+ try {
+ desc = new DeploymentProviderDescriptor(element, descText);
+ providers.put(desc.getId(), desc);
+ } catch (CoreException e) {
+ TeamPlugin.log(e);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public DeploymentProviderDescriptor[] getTeamProviderDescriptors() {
+ return (DeploymentProviderDescriptor[])providers.values().toArray(new DeploymentProviderDescriptor[providers.size()]);
+ }
+
+ public DeploymentProviderDescriptor find(String id) {
+ return (DeploymentProviderDescriptor)providers.get(id);
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/RegistryReader.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/RegistryReader.java
new file mode 100644
index 000000000..afb5dbef1
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/registry/RegistryReader.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.registry;
+
+import java.util.Hashtable;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.internal.core.Sorter;
+import org.eclipse.team.internal.core.TeamPlugin;
+
+public abstract class RegistryReader {
+ protected static final String TAG_DESCRIPTION = "description"; //$NON-NLS-1$
+ protected static Hashtable extensionPoints = new Hashtable();
+ /**
+ * The constructor.
+ */
+ protected RegistryReader() {
+ }
+ /**
+ * This method extracts description as a subelement of the given element.
+ *
+ * @return description string if defined, or empty string if not.
+ */
+ protected String getDescription(IConfigurationElement config) {
+ IConfigurationElement[] children = config.getChildren(TAG_DESCRIPTION);
+ if (children.length >= 1) {
+ return children[0].getValue();
+ }
+ return ""; //$NON-NLS-1$
+ }
+ /**
+ * Logs the error in the workbench log using the provided text and the
+ * information in the configuration element.
+ */
+ protected void logError(IConfigurationElement element, String text) {
+ IExtension extension = element.getDeclaringExtension();
+ IPluginDescriptor descriptor = extension.getDeclaringPluginDescriptor();
+ StringBuffer buf = new StringBuffer();
+ buf.append("Plugin " + descriptor.getUniqueIdentifier() + ", extension " + extension.getExtensionPointUniqueIdentifier()); //$NON-NLS-2$//$NON-NLS-1$
+ buf.append("\n" + text); //$NON-NLS-1$
+ TeamPlugin.log(IStatus.ERROR, buf.toString(), null);
+ }
+ /**
+ * Logs a very common registry error when a required attribute is missing.
+ */
+ protected void logMissingAttribute(IConfigurationElement element, String attributeName) {
+ logError(element, "Required attribute '" + attributeName + "' not defined"); //$NON-NLS-2$//$NON-NLS-1$
+ }
+
+ /**
+ * Logs a very common registry error when a required child is missing.
+ */
+ protected void logMissingElement(IConfigurationElement element, String elementName) {
+ logError(element, "Required sub element '" + elementName + "' not defined"); //$NON-NLS-2$//$NON-NLS-1$
+ }
+
+ /**
+ * Logs a registry error when the configuration element is unknown.
+ */
+ protected void logUnknownElement(IConfigurationElement element) {
+ logError(element, "Unknown extension tag found: " + element.getName()); //$NON-NLS-1$
+ }
+ /**
+ * Apply a reproducable order to the list of extensions provided, such that
+ * the order will not change as extensions are added or removed.
+ */
+ protected IExtension[] orderExtensions(IExtension[] extensions) {
+ // By default, the order is based on plugin id sorted
+ // in ascending order. The order for a plugin providing
+ // more than one extension for an extension point is
+ // dependent in the order listed in the XML file.
+ Sorter sorter = new Sorter() {
+ public boolean compare(Object extension1, Object extension2) {
+ String s1 = ((IExtension) extension1).getDeclaringPluginDescriptor().getUniqueIdentifier();
+ String s2 = ((IExtension) extension2).getDeclaringPluginDescriptor().getUniqueIdentifier();
+ //Return true if elementTwo is 'greater than' elementOne
+ return s2.compareToIgnoreCase(s1) > 0;
+ }
+ };
+
+ Object[] sorted = sorter.sort(extensions);
+ IExtension[] sortedExtension = new IExtension[sorted.length];
+ System.arraycopy(sorted, 0, sortedExtension, 0, sorted.length);
+ return sortedExtension;
+ }
+ /**
+ * Implement this method to read element's attributes. If children should
+ * also be read, then implementor is responsible for calling <code>readElementChildren</code>.
+ * Implementor is also responsible for logging missing attributes.
+ *
+ * @return true if element was recognized, false if not.
+ */
+ protected abstract boolean readElement(IConfigurationElement element);
+ /**
+ * Read the element's children. This is called by the subclass' readElement
+ * method when it wants to read the children of the element.
+ */
+ protected void readElementChildren(IConfigurationElement element) {
+ readElements(element.getChildren());
+ }
+ /**
+ * Read each element one at a time by calling the subclass implementation
+ * of <code>readElement</code>.
+ *
+ * Logs an error if the element was not recognized.
+ */
+ protected void readElements(IConfigurationElement[] elements) {
+ for (int i = 0; i < elements.length; i++) {
+ if (!readElement(elements[i]))
+ logUnknownElement(elements[i]);
+ }
+ }
+ /**
+ * Read one extension by looping through its configuration elements.
+ */
+ protected void readExtension(IExtension extension) {
+ readElements(extension.getConfigurationElements());
+ }
+ /**
+ * Start the registry reading process using the supplied plugin ID and
+ * extension point.
+ */
+ public void readRegistry(IPluginRegistry registry, String pluginId, String extensionPoint) {
+ String pointId = pluginId + "-" + extensionPoint; //$NON-NLS-1$
+ IExtension[] extensions = (IExtension[]) extensionPoints.get(pointId);
+ if (extensions == null) {
+ IExtensionPoint point = registry.getExtensionPoint(pluginId, extensionPoint);
+ if (point == null)
+ return;
+ extensions = point.getExtensions();
+ extensionPoints.put(pointId, extensions);
+ }
+ for (int i = 0; i < extensions.length; i++)
+ readExtension(extensions[i]);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/ContentComparator.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/ContentComparator.java
new file mode 100644
index 000000000..44922f9fa
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/ContentComparator.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import java.io.*;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.*;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.IResourceVariant;
+import org.eclipse.team.internal.core.Policy;
+import org.eclipse.team.internal.core.TeamPlugin;
+
+/**
+ * This is an internal class that is usd by the <code>ContentComparisonSyncInfoFilter</code>
+ * to compare the comtents of the local and remote resources
+ */
+public class ContentComparator {
+
+ private boolean ignoreWhitespace = false;
+
+ public ContentComparator(boolean ignoreWhitespace) {
+ this.ignoreWhitespace = ignoreWhitespace;
+ }
+
+ public boolean compare(Object e1, Object e2, IProgressMonitor monitor) {
+ InputStream is1 = null;
+ InputStream is2 = null;
+ try {
+ is1 = getContents(e1, Policy.subMonitorFor(monitor, 50));
+ is2 = getContents(e2, Policy.subMonitorFor(monitor, 50));
+ return contentsEqual(is1, is2, shouldIgnoreWhitespace());
+ } catch(TeamException e) {
+ TeamPlugin.log(e);
+ return false;
+ } finally {
+ try {
+ try {
+ if (is1 != null) {
+ is1.close();
+ }
+ } finally {
+ if (is2 != null) {
+ is2.close();
+ }
+ }
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+
+ protected boolean shouldIgnoreWhitespace() {
+ return ignoreWhitespace;
+ }
+
+ /**
+ * Returns <code>true</code> if both input streams byte contents is
+ * identical.
+ *
+ * @param input1
+ * first input to contents compare
+ * @param input2
+ * second input to contents compare
+ * @return <code>true</code> if content is equal
+ */
+ private boolean contentsEqual(InputStream is1, InputStream is2, boolean ignoreWhitespace) {
+ try {
+ if (is1 == is2)
+ return true;
+
+ if (is1 == null && is2 == null) // no byte contents
+ return true;
+
+ if (is1 == null || is2 == null) // only one has
+ // contents
+ return false;
+
+ while (true) {
+ int c1 = is1.read();
+ while (shouldIgnoreWhitespace() && isWhitespace(c1))
+ c1 = is1.read();
+ int c2 = is2.read();
+ while (shouldIgnoreWhitespace() && isWhitespace(c2))
+ c2 = is2.read();
+ if (c1 == -1 && c2 == -1)
+ return true;
+ if (c1 != c2)
+ break;
+
+ }
+ } catch (IOException ex) {
+ } finally {
+ try {
+ try {
+ if (is1 != null) {
+ is1.close();
+ }
+ } finally {
+ if (is2 != null) {
+ is2.close();
+ }
+ }
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ return false;
+ }
+
+ private boolean isWhitespace(int c) {
+ if (c == -1)
+ return false;
+ return Character.isWhitespace((char) c);
+ }
+
+ private InputStream getContents(Object resource, IProgressMonitor monitor) throws TeamException {
+ try {
+ if (resource instanceof IFile) {
+ return new BufferedInputStream(((IFile) resource).getContents());
+ } else if(resource instanceof IResourceVariant) {
+ IResourceVariant remote = (IResourceVariant)resource;
+ if (!remote.isContainer()) {
+ return new BufferedInputStream(remote.getStorage(monitor).getContents());
+ }
+ }
+ return null;
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberEventHandler.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberEventHandler.java
new file mode 100644
index 000000000..faebc4de7
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberEventHandler.java
@@ -0,0 +1,431 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import java.util.*;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.team.core.*;
+import org.eclipse.team.core.subscribers.Subscriber;
+import org.eclipse.team.core.synchronize.SyncInfo;
+import org.eclipse.team.internal.core.*;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * This handler collects changes and removals to resources and calculates their
+ * synchronization state in a background job. The result is fed input the SyncSetInput.
+ *
+ * Exceptions that occur when the job is processing the events are collected and
+ * returned as part of the Job's status.
+ */
+public class SubscriberEventHandler extends BackgroundEventHandler {
+ // The set that receives notification when the resource synchronization state
+ // has been calculated by the job.
+ private SyncSetInputFromSubscriber syncSetInput;
+
+ // Changes accumulated by the event handler
+ private List resultCache = new ArrayList();
+
+ private boolean started = false;
+
+ private IProgressMonitor progressGroup;
+
+ private int ticks;
+
+ /**
+ * Internal resource synchronization event. Can contain a result.
+ */
+ class SubscriberEvent extends Event{
+ static final int REMOVAL = 1;
+ static final int CHANGE = 2;
+ static final int INITIALIZE = 3;
+ SyncInfo result;
+
+ SubscriberEvent(IResource resource, int type, int depth) {
+ super(resource, type, depth);
+ }
+ public SubscriberEvent(
+ IResource resource,
+ int type,
+ int depth,
+ SyncInfo result) {
+ this(resource, type, depth);
+ this.result = result;
+ }
+ public SyncInfo getResult() {
+ return result;
+ }
+ protected String getTypeString() {
+ switch (getType()) {
+ case REMOVAL :
+ return "REMOVAL"; //$NON-NLS-1$
+ case CHANGE :
+ return "CHANGE"; //$NON-NLS-1$
+ case INITIALIZE :
+ return "INITIALIZE"; //$NON-NLS-1$
+ default :
+ return "INVALID"; //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * This is a special event used to reset and connect sync sets.
+ * The preemtive flag is used to indicate that the runnable should take
+ * the highest priority and thus be placed on the front of the queue
+ * and be processed as soon as possible, preemting any event that is currently
+ * being processed. The curent event will continue processing once the
+ * high priority event has been processed
+ */
+ public class RunnableEvent extends Event {
+ static final int RUNNABLE = 1000;
+ private IWorkspaceRunnable runnable;
+ private boolean preemtive;
+ public RunnableEvent(IWorkspaceRunnable runnable, boolean preemtive) {
+ super(ResourcesPlugin.getWorkspace().getRoot(), RUNNABLE, IResource.DEPTH_ZERO);
+ this.runnable = runnable;
+ this.preemtive = preemtive;
+ }
+ public void run(IProgressMonitor monitor) throws CoreException {
+ runnable.run(monitor);
+ }
+ public boolean isPreemtive() {
+ return preemtive;
+ }
+ }
+
+ /**
+ * Create a handler. This will initialize all resources for the subscriber associated with
+ * the set.
+ * @param set the subscriber set to feed changes into
+ */
+ public SubscriberEventHandler(Subscriber subscriber) {
+ super(
+ Policy.bind("SubscriberEventHandler.jobName", subscriber.getName()), //$NON-NLS-1$
+ Policy.bind("SubscriberEventHandler.errors", subscriber.getName())); //$NON-NLS-1$
+ this.syncSetInput = new SyncSetInputFromSubscriber(subscriber, this);
+ }
+
+ /**
+ * Start the event handler by queuing events to prime the sync set input with the out-of-sync
+ * resources of the subscriber.
+ */
+ public synchronized void start() {
+ // Set the started flag to enable event queueing.
+ // We are gaurenteed to be the first since this method is synchronized.
+ started = true;
+ reset(syncSetInput.getSubscriber().roots(), SubscriberEvent.INITIALIZE);
+ }
+
+ protected synchronized void queueEvent(Event event, boolean front) {
+ // Only post events if the handler is started
+ if (started) {
+ super.queueEvent(event, front);
+ }
+ }
+ /**
+ * Schedule the job or process the events now.
+ */
+ public void schedule() {
+ Job job = getEventHandlerJob();
+ if(progressGroup != null) {
+ job.setSystem(false);
+ job.setProgressGroup(progressGroup, ticks);
+ } else {
+ job.setSystem(true);
+ }
+ getEventHandlerJob().schedule();
+ }
+
+ /**
+ * Initialize all resources for the subscriber associated with the set. This will basically recalculate
+ * all synchronization information for the subscriber.
+ * <p>
+ * This method is sycnrhonized with the queueEvent method to ensure that the two events
+ * queued by this method are back-to-back
+ */
+ public synchronized void reset(IResource[] roots) {
+ if (roots == null) {
+ roots = syncSetInput.getSubscriber().roots();
+ }
+ // First, reset the sync set input to clear the sync set
+ run(new IWorkspaceRunnable() {
+ public void run(IProgressMonitor monitor) throws CoreException {
+ syncSetInput.reset(monitor);
+ }
+ }, false /* keep ordering the same */);
+ // Then, prime the set from the subscriber
+ reset(roots, SubscriberEvent.CHANGE);
+ }
+
+ /**
+ * Called by a client to indicate that a resource has changed and its synchronization state
+ * should be recalculated.
+ * @param resource the changed resource
+ * @param depth the depth of the change calculation
+ */
+ public void change(IResource resource, int depth) {
+ queueEvent(new SubscriberEvent(resource, SubscriberEvent.CHANGE, depth), false);
+ }
+
+ /**
+ * Called by a client to indicate that a resource has been removed and should be removed. The
+ * removal will propagate to the set.
+ * @param resource the resource that was removed
+ */
+ public void remove(IResource resource) {
+ queueEvent(
+ new SubscriberEvent(resource, SubscriberEvent.REMOVAL, IResource.DEPTH_INFINITE), false);
+ }
+
+ /**
+ * Collect the calculated synchronization information for the given resource at the given depth. The
+ * results are added to the provided list.
+ */
+ private void collect(
+ IResource resource,
+ int depth,
+ IProgressMonitor monitor) {
+
+ // handle any preemtive events before continuing
+ handlePreemptiveEvents(monitor);
+
+ if (resource.getType() != IResource.FILE
+ && depth != IResource.DEPTH_ZERO) {
+ try {
+ IResource[] members =
+ syncSetInput.getSubscriber().members(resource);
+ for (int i = 0; i < members.length; i++) {
+ collect(
+ members[i],
+ depth == IResource.DEPTH_INFINITE
+ ? IResource.DEPTH_INFINITE
+ : IResource.DEPTH_ZERO,
+ monitor);
+ }
+ } catch (TeamException e) {
+ handleException(e, resource, ITeamStatus.SYNC_INFO_SET_ERROR, Policy.bind("SubscriberEventHandler.8", resource.getFullPath().toString(), e.getMessage())); //$NON-NLS-1$
+ }
+ }
+
+ monitor.subTask(Policy.bind("SubscriberEventHandler.2", resource.getFullPath().toString())); //$NON-NLS-1$
+ try {
+ SyncInfo info = syncSetInput.getSubscriber().getSyncInfo(resource);
+ // resource is no longer under the subscriber control
+ if (info == null) {
+ resultCache.add(
+ new SubscriberEvent(resource, SubscriberEvent.REMOVAL, IResource.DEPTH_ZERO));
+ } else {
+ resultCache.add(
+ new SubscriberEvent(resource, SubscriberEvent.CHANGE, IResource.DEPTH_ZERO, info));
+ }
+ handlePendingDispatch(monitor);
+ } catch (TeamException e) {
+ handleException(e, resource, ITeamStatus.RESOURCE_SYNC_INFO_ERROR, Policy.bind("SubscriberEventHandler.9", resource.getFullPath().toString(), e.getMessage())); //$NON-NLS-1$
+ }
+ monitor.worked(1);
+ }
+
+ private void handlePendingDispatch(IProgressMonitor monitor) {
+ if (isReadyForDispatch(false /*don't wait if queue is empty*/)) {
+ dispatchEvents(Policy.subMonitorFor(monitor, 5));
+ eventsDispatched();
+ }
+ }
+
+ /*
+ * Handle the exception by returning it as a status from the job but also by
+ * dispatching it to the sync set input so any down stream views can react
+ * accordingly.
+ */
+ private void handleException(CoreException e, IResource resource, int code, String message) {
+ handleException(e);
+ syncSetInput.handleError(new TeamStatus(IStatus.ERROR, TeamPlugin.ID, code, message, e, resource));
+ }
+
+ /**
+ * Called to initialize to calculate the synchronization information using the optimized subscriber method. For
+ * subscribers that don't support the optimization, all resources in the subscriber are manually re-calculated.
+ * @param resources the resources to check
+ * @param depth the depth
+ * @param monitor
+ * @return Event[] the change events
+ * @throws TeamException
+ */
+ private void collectAll(
+ IResource resource,
+ int depth,
+ IProgressMonitor monitor) {
+
+ monitor.beginTask(null, 100);
+ try {
+ SyncInfo[] infos = null;
+ try {
+ infos = syncSetInput.getSubscriber().getAllOutOfSync(new IResource[] { resource }, depth, Policy.subMonitorFor(monitor, 10));
+ } catch (TeamException e) {
+ // Log the exception and fallback to using hierarchical search
+ TeamPlugin.log(e);
+ }
+
+ // The subscriber hasn't cached out-of-sync resources. We will have to
+ // traverse all resources and calculate their state.
+ if (infos == null) {
+ IProgressMonitor subMonitor = Policy.infiniteSubMonitorFor(monitor, 90);
+ subMonitor.beginTask(null, 20);
+ collect(
+ resource,
+ IResource.DEPTH_INFINITE,
+ subMonitor);
+ } else {
+ // The subscriber has returned the list of out-of-sync resources.
+ for (int i = 0; i < infos.length; i++) {
+ SyncInfo info = infos[i];
+ resultCache.add(
+ new SubscriberEvent(info.getLocal(), SubscriberEvent.CHANGE, depth, info));
+ }
+ }
+ } finally {
+ monitor.done();
+ }
+ }
+
+ /**
+ * Feed the given events to the set. The appropriate method on the set is called
+ * for each event type.
+ * @param events
+ */
+ private void dispatchEvents(SubscriberEvent[] events, IProgressMonitor monitor) {
+ // this will batch the following set changes until endInput is called.
+ try {
+ syncSetInput.getSyncSet().beginInput();
+ for (int i = 0; i < events.length; i++) {
+ SubscriberEvent event = events[i];
+ switch (event.getType()) {
+ case SubscriberEvent.CHANGE :
+ syncSetInput.collect(event.getResult(), monitor);
+ break;
+ case SubscriberEvent.REMOVAL :
+ syncSetInput.getSyncSet().remove(event.getResource(), event.getDepth());
+ break;
+ }
+ }
+ } finally {
+ syncSetInput.getSyncSet().endInput(monitor);
+ }
+ }
+
+ /**
+ * Initialize all resources for the subscriber associated with the set. This will basically recalculate
+ * all synchronization information for the subscriber.
+ * @param type can be Event.CHANGE to recalculate all states or Event.INITIALIZE to perform the
+ * optimized recalculation if supported by the subscriber.
+ */
+ private void reset(IResource[] roots, int type) {
+ IResource[] resources = roots;
+ for (int i = 0; i < resources.length; i++) {
+ queueEvent(new SubscriberEvent(resources[i], type, IResource.DEPTH_INFINITE), false);
+ }
+ }
+
+ protected void processEvent(Event event, IProgressMonitor monitor) {
+ try {
+ // Cancellation is dangerous because this will leave the sync info in a bad state.
+ // Purposely not checking -
+ int type = event.getType();
+ switch (type) {
+ case RunnableEvent.RUNNABLE :
+ executeRunnable(event, monitor);
+ break;
+ case SubscriberEvent.REMOVAL :
+ resultCache.add(event);
+ break;
+ case SubscriberEvent.CHANGE :
+ collect(
+ event.getResource(),
+ event.getDepth(),
+ monitor);
+ break;
+ case SubscriberEvent.INITIALIZE :
+ getEventHandlerJob().setSystem(false);
+ monitor.subTask(Policy.bind("SubscriberEventHandler.2", event.getResource().getFullPath().toString())); //$NON-NLS-1$
+ collectAll(
+ event.getResource(),
+ event.getDepth(),
+ Policy.subMonitorFor(monitor, 64));
+ break;
+ }
+ } catch (RuntimeException e) {
+ // handle the exception and keep processing
+ handleException(new TeamException(Policy.bind("SubscriberEventHandler.10"), e), event.getResource(), ITeamStatus.SYNC_INFO_SET_ERROR, Policy.bind("SubscriberEventHandler.11", event.getResource().getFullPath().toString(), e.getMessage())); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ /*
+ * Execute the RunnableEvent
+ */
+ private void executeRunnable(Event event, IProgressMonitor monitor) {
+ // Dispatch any queued results to clear pending output events
+ dispatchEvents(Policy.subMonitorFor(monitor, 1));
+ eventsDispatched();
+ try {
+ ((RunnableEvent)event).run(Policy.subMonitorFor(monitor, 1));
+ } catch (CoreException e) {
+ handleException(e, event.getResource(), ITeamStatus.SYNC_INFO_SET_ERROR, e.getMessage());
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.subscribers.BackgroundEventHandler#dispatchEvents()
+ */
+ protected void dispatchEvents(IProgressMonitor monitor) {
+ if (!resultCache.isEmpty()) {
+ dispatchEvents((SubscriberEvent[]) resultCache.toArray(new SubscriberEvent[resultCache.size()]), monitor);
+ resultCache.clear();
+ }
+ }
+
+ /**
+ * Queue up the given runnable in an event to be processed by this job
+ * @param runnable the runnable to be run by the handler
+ */
+ public void run(IWorkspaceRunnable runnable, boolean frontOnQueue) {
+ queueEvent(new RunnableEvent(runnable, frontOnQueue), frontOnQueue);
+ }
+
+ /**
+ * Return the sync set input that was created by this event handler
+ * @return
+ */
+ public SyncSetInputFromSubscriber getSyncSetInput() {
+ return syncSetInput;
+ }
+
+ public void setProgressGroupHint(IProgressMonitor progressGroup, int ticks) {
+ this.progressGroup = progressGroup;
+ this.ticks = ticks;
+ }
+
+ /**
+ * @return Returns the started.
+ */
+ protected boolean isStarted() {
+ return started;
+ }
+
+ private void handlePreemptiveEvents(IProgressMonitor monitor) {
+ Event event = peek();
+ if (event instanceof RunnableEvent && ((RunnableEvent)event).isPreemtive()) {
+ executeRunnable(nextElement(), monitor);
+ }
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberSyncInfoSet.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberSyncInfoSet.java
new file mode 100644
index 000000000..99fd3ff20
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SubscriberSyncInfoSet.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener;
+import org.eclipse.team.core.synchronize.SyncInfoTree;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * The <code>SubscriberSyncInfoSet</code> is a <code>SyncInfoSet</code> that provides the ability to add,
+ * remove and change <code>SyncInfo</code> and fires change event notifications to registered listeners.
+ * It also provides the ability
+ * to batch changes in a single change notification as well as optimizations for sync info retrieval.
+ *
+ * This class uses synchronized methods and synchronized blocks to protect internal data structures during both access
+ * and modify operations and uses an <code>ILock</code> to make modification operations thread-safe. The events
+ * are fired while this lock is held so clients responding to these events should not obtain their own internal locks
+ * while processing change events.
+ *
+ * TODO: Override modification methods to enforce use with handler
+ *
+ */
+public class SubscriberSyncInfoSet extends SyncInfoTree {
+
+ protected SubscriberEventHandler handler;
+
+ public SubscriberSyncInfoSet(SubscriberEventHandler handler) {
+ this.handler = handler;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.synchronize.SyncInfoSet#connect(org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void connect(ISyncInfoSetChangeListener listener, IProgressMonitor monitor) {
+ if (handler == null) {
+ super.connect(listener, monitor);
+ } else {
+ connect(listener);
+ }
+ }
+
+ /**
+ * Variation of connect that does not need progress and does not throw an exception.
+ * Progress is provided by the background event handler and errors are passed through
+ * the chain to the view.
+ * @param listener
+ */
+ public void connect(final ISyncInfoSetChangeListener listener) {
+ if (handler == null) {
+ // Should only use this connect if the set has a handler
+ throw new UnsupportedOperationException();
+ } else {
+ handler.run(new IWorkspaceRunnable() {
+ public void run(IProgressMonitor monitor) {
+ try {
+ beginInput();
+ monitor.beginTask(null, 100);
+ removeSyncSetChangedListener(listener);
+ addSyncSetChangedListener(listener);
+ listener.syncInfoSetReset(SubscriberSyncInfoSet.this, Policy.subMonitorFor(monitor, 95));
+ } finally {
+ endInput(Policy.subMonitorFor(monitor, 5));
+ monitor.done();
+ }
+ }
+ }, true /* high priority */);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoStatistics.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoStatistics.java
new file mode 100644
index 000000000..7372b400f
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoStatistics.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.team.core.synchronize.SyncInfo;
+
+/**
+ * Counts SyncInfo states and allows for easy querying for different sync states.
+ */
+public class SyncInfoStatistics {
+ // {int sync kind -> int number of infos with that sync kind in this sync set}
+ protected Map stats = new HashMap();
+
+ /**
+ * Count this sync kind. Only the type of the sync info is stored.
+ * @param info the new info
+ */
+ public void add(SyncInfo info) {
+ // update statistics
+ Long count = (Long)stats.get(new Integer(info.getKind()));
+ if(count == null) {
+ count = new Long(0);
+ }
+ stats.put(new Integer(info.getKind()), new Long(count.longValue() + 1));
+ }
+
+ /**
+ * Remove this sync kind.
+ * @param info the info type to remove
+ */
+ public void remove(SyncInfo info) {
+ // update stats
+ Integer kind = new Integer(info.getKind());
+ Long count = (Long)stats.get(kind);
+ if(count == null) {
+ // error condition, shouldn't be removing if we haven't added yet
+ // programmer error calling remove before add.
+ } else {
+ long newCount = count.intValue() - 1;
+ if(newCount > 0) {
+ stats.put(kind, new Long(newCount));
+ } else {
+ stats.remove(kind);
+ }
+ }
+ }
+
+ /**
+ * Return the count of sync infos for the specified sync kind. A mask can be used to acucmulate
+ * counts for specific directions or change types.
+ * To return the number of outgoing changes:
+ * long outgoingChanges = stats.countFor(SyncInfo.OUTGOING, SyncInfo.DIRECTION_MASK);
+ *
+ * @param kind the sync kind for which to return the count
+ * @param mask the mask applied to the stored sync kind
+ * @return the number of sync info types added for the specific kind
+ */
+ public long countFor(int kind, int mask) {
+ if(mask == 0) {
+ Long count = (Long)stats.get(new Integer(kind));
+ return count == null ? 0 : count.longValue();
+ } else {
+ Iterator it = stats.keySet().iterator();
+ long count = 0;
+ while (it.hasNext()) {
+ Integer key = (Integer) it.next();
+ if((key.intValue() & mask) == kind) {
+ count += ((Long)stats.get(key)).intValue();
+ }
+ }
+ return count;
+ }
+ }
+
+ /**
+ * Clear the statistics counts. All calls to countFor() will return 0 until new
+ * sync infos are added.
+ */
+ public void clear() {
+ stats.clear();
+ }
+
+ /**
+ * For debugging
+ */
+ public String toString() {
+ StringBuffer out = new StringBuffer();
+ Iterator it = stats.keySet().iterator();
+ while (it.hasNext()) {
+ Integer kind = (Integer) it.next();
+ out.append(SyncInfo.kindToString(kind.intValue()) + ": " + ((Long)stats.get(kind)) + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return out.toString();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoTreeChangeEvent.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoTreeChangeEvent.java
new file mode 100644
index 000000000..5cb06dd60
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoTreeChangeEvent.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import java.util.*;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.synchronize.ISyncInfoTreeChangeEvent;
+import org.eclipse.team.core.synchronize.SyncInfoSet;
+
+public class SyncInfoTreeChangeEvent extends SyncSetChangedEvent implements ISyncInfoTreeChangeEvent {
+
+ private Set removedSubtrees = new HashSet();
+ private Set addedSubtrees = new HashSet();
+
+ public SyncInfoTreeChangeEvent(SyncInfoSet set) {
+ super(set);
+ }
+
+ public void removedSubtreeRoot(IResource root) {
+ if (addedSubtrees.contains(root)) {
+ // The root was added and removed which is a no-op
+ addedSubtrees.remove(root);
+ } else if (isDescendantOfAddedRoot(root)) {
+ // Nothing needs to be done since no listeners ever knew about the root
+ } else {
+ // check if the root is a child of an existing root
+ // (in which case it need not be added).
+ // Also, remove any exisiting roots that are children
+ // of the new root
+ for (Iterator iter = removedSubtrees.iterator(); iter.hasNext();) {
+ IResource element = (IResource) iter.next();
+ // check if the root is already in the list
+ if (root.equals(element)) return;
+ if (isParent(root, element)) {
+ // the root invalidates the current element
+ iter.remove();
+ } else if (isParent(element, root)) {
+ // the root is a child of an existing element
+ return;
+ }
+ }
+ removedSubtrees.add(root);
+ }
+ }
+
+ private boolean isParent(IResource root, IResource element) {
+ return root.getFullPath().isPrefixOf(element.getFullPath());
+ }
+
+ public void addedSubtreeRoot(IResource parent) {
+ if (removedSubtrees.contains(parent)) {
+ // The root was re-added. Just removing the removedRoot
+ // may not give the proper event.
+ // Since we can't be sure, just force a reset.
+ reset();
+ } else {
+ // only add the root if their isn't a higher root in the list already
+ if (!isDescendantOfAddedRoot(parent)) {
+ addedSubtrees.add(parent);
+ }
+ }
+ }
+
+ private boolean isDescendantOfAddedRoot(IResource resource) {
+ for (Iterator iter = addedSubtrees.iterator(); iter.hasNext();) {
+ IResource root = (IResource) iter.next();
+ if (isParent(root, resource)) {
+ // There is a higher added root already in the list
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public IResource[] getAddedSubtreeRoots() {
+ return (IResource[]) addedSubtrees.toArray(new IResource[addedSubtrees.size()]);
+ }
+
+ public IResource[] getRemovedSubtreeRoots() {
+ return (IResource[]) removedSubtrees.toArray(new IResource[removedSubtrees.size()]);
+ }
+
+ public boolean isEmpty() {
+ return super.isEmpty() && removedSubtrees.isEmpty() && addedSubtrees.isEmpty();
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoWorkingSetFilter.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoWorkingSetFilter.java
new file mode 100644
index 000000000..ca632a6b0
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncInfoWorkingSetFilter.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import java.util.*;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.*;
+import org.eclipse.team.core.synchronize.*;
+import org.eclipse.team.internal.core.TeamPlugin;
+
+/**
+ * WorkingSet filter for a SyncSet.
+ */
+public class SyncInfoWorkingSetFilter extends FastSyncInfoFilter {
+
+ private IResource[] resources;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ui.sync.SyncInfoFilter#select(org.eclipse.team.core.subscribers.SyncInfo)
+ */
+ public boolean select(SyncInfo info) {
+ // if there's no set, the resource is included
+ if (isEmpty()) return true;
+ return isIncluded(info.getLocal());
+ }
+
+ /*
+ * Answer true if the given resource is included in the working set
+ */
+ private boolean isIncluded(IResource resource) {
+ // otherwise, if their is a parent of the resource in the set,
+ // it is included
+ List result = new ArrayList();
+ for (int i = 0; i < resources.length; i++) {
+ IResource setResource = resources[i];
+ if (isParent(setResource, resource)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isParent(IResource parent, IResource child) {
+ return (parent.getFullPath().isPrefixOf(child.getFullPath()));
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.views.SyncSetInputFromSubscriber#getRoots()
+ */
+ public IResource[] getRoots(Subscriber subscriber) {
+ IResource[] roots = subscriber.roots();
+ if (isEmpty()) return roots;
+
+ // filter the roots by the selected working set
+ Set result = new HashSet();
+ for (int i = 0; i < roots.length; i++) {
+ IResource resource = roots[i];
+ result.addAll(Arrays.asList(getIntersectionWithSet(subscriber, resource)));
+ }
+ return (IResource[]) result.toArray(new IResource[result.size()]);
+ }
+
+ /*
+ * Answer the intersection between the given resource and it's children
+ * and the receiver's working set.
+ */
+ private IResource[] getIntersectionWithSet(Subscriber subscriber, IResource resource) {
+ List result = new ArrayList();
+ for (int i = 0; i < resources.length; i++) {
+ IResource setResource = resources[i];
+ if (setResource != null) {
+ if (isParent(resource, setResource)) {
+ try {
+ if (subscriber.isSupervised(setResource)) {
+ result.add(setResource);
+ }
+ } catch (TeamException e) {
+ // Log the exception and add the resource to the list
+ TeamPlugin.log(e);
+ result.add(setResource);
+ }
+ } else if (isParent(setResource, resource)) {
+ result.add(resource);
+ }
+ }
+ }
+ return (IResource[]) result.toArray(new IResource[result.size()]);
+ }
+
+ public void setWorkingSet(IResource[] resources) {
+ this.resources = resources;
+ }
+
+ public IResource[] getWorkingSet() {
+ return this.resources;
+ }
+
+ private boolean isEmpty() {
+ return resources == null || resources.length == 0;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetChangedEvent.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetChangedEvent.java
new file mode 100644
index 000000000..1581ba012
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetChangedEvent.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import java.util.*;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.ITeamStatus;
+import org.eclipse.team.core.synchronize.*;
+
+/**
+ * This event keeps track of the changes in a sync set
+ */
+public class SyncSetChangedEvent implements ISyncInfoSetChangeEvent {
+
+ private SyncInfoSet set;
+
+ // List that accumulate changes
+ // SyncInfo
+ private Set changedResources = new HashSet();
+ private Set removedResources = new HashSet();
+ private Set addedResources = new HashSet();
+
+ private boolean reset = false;
+
+ private boolean errorAdded;
+
+ private List errors = new ArrayList();
+
+ public SyncSetChangedEvent(SyncInfoSet set) {
+ super();
+ this.set = set;
+ }
+
+ public void added(SyncInfo info) {
+ if (removedResources.contains(info.getLocal())) {
+ // A removal followed by an addition is treated as a change
+ removedResources.remove(info.getLocal());
+ changed(info);
+ } else {
+ addedResources.add(info);
+ }
+ }
+
+ public void removed(IResource resource, SyncInfo info) {
+ if (changedResources.contains(info)) {
+ // No use in reporting the change since it has subsequently been removed
+ changedResources.remove(info);
+ } else if (addedResources.contains(info)) {
+ // An addition followed by a removal can be dropped
+ addedResources.remove(info);
+ return;
+ }
+ removedResources.add(resource);
+ }
+
+ public void changed(SyncInfo info) {
+ changedResources.add(info);
+ }
+
+ public SyncInfo[] getAddedResources() {
+ return (SyncInfo[]) addedResources.toArray(new SyncInfo[addedResources.size()]);
+ }
+
+ public SyncInfo[] getChangedResources() {
+ return (SyncInfo[]) changedResources.toArray(new SyncInfo[changedResources.size()]);
+ }
+
+ public IResource[] getRemovedResources() {
+ return (IResource[]) removedResources.toArray(new IResource[removedResources.size()]);
+ }
+
+ public SyncInfoSet getSet() {
+ return set;
+ }
+
+ public void reset() {
+ reset = true;
+ }
+
+ public boolean isReset() {
+ return reset;
+ }
+
+ public boolean isEmpty() {
+ return changedResources.isEmpty() && removedResources.isEmpty() && addedResources.isEmpty() && errors.isEmpty();
+ }
+
+ public void errorOccurred(ITeamStatus status) {
+ errors.add(status);
+ }
+
+ public ITeamStatus[] getErrors() {
+ return (ITeamStatus[]) errors.toArray(new ITeamStatus[errors.size()]);
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInput.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInput.java
new file mode 100644
index 000000000..3935cffe8
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInput.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.*;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * This is the superclass for all SyncSet input providers
+ */
+public abstract class SyncSetInput {
+
+ private SubscriberSyncInfoSet syncSet;
+ private SyncInfoFilter filter = new FastSyncInfoFilter();
+
+ public SyncSetInput(SubscriberEventHandler handler) {
+ syncSet = new SubscriberSyncInfoSet(handler);
+ }
+
+ public SubscriberSyncInfoSet getSyncSet() {
+ return syncSet;
+ }
+
+ /**
+ * This method is invoked from reset to get all the sync information from
+ * the input source.
+ */
+ protected abstract void fetchInput(IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * The input is no longer being used. Disconnect it from its source.
+ */
+ public abstract void disconnect();
+
+ /**
+ * Reset the input. This will clear the current contents of the sync set and
+ * obtain the contents from the input source.
+ */
+ public void reset(IProgressMonitor monitor) throws TeamException {
+ syncSet.beginInput();
+ try {
+ monitor = Policy.monitorFor(monitor);
+ monitor.beginTask(null, 100);
+ syncSet.clear();
+ fetchInput(Policy.subMonitorFor(monitor, 90));
+ } finally {
+ syncSet.endInput(Policy.subMonitorFor(monitor, 10));
+ monitor.done();
+ }
+ }
+
+ /**
+ * Collect the change in the provided sync info.
+ */
+ protected void collect(SyncInfo info, IProgressMonitor monitor) {
+ boolean isOutOfSync = filter.select(info, monitor);
+ SyncInfo oldInfo = syncSet.getSyncInfo(info.getLocal());
+ boolean wasOutOfSync = oldInfo != null;
+ if (isOutOfSync) {
+ syncSet.add(info);
+ } else if (wasOutOfSync) {
+ syncSet.remove(info.getLocal());
+ }
+ }
+
+ protected void remove(IResource resource) {
+ SyncInfo oldInfo = syncSet.getSyncInfo(resource);
+ boolean wasOutOfSync = oldInfo != null;
+ if (oldInfo != null) {
+ syncSet.remove(resource);
+ }
+ }
+
+ public SyncInfoFilter getFilter() {
+ return filter;
+ }
+
+ public void setFilter(SyncInfoFilter filter) {
+ this.filter = filter;
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSubscriber.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSubscriber.java
new file mode 100644
index 000000000..19f03d931
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSubscriber.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.ITeamStatus;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.Subscriber;
+
+/**
+ * Records resource synchronization changes from a Team subscriber. The actual changes
+ * are calculated via the SubscriberEventHandler and stored in this input.
+ */
+public class SyncSetInputFromSubscriber extends SyncSetInput {
+
+ private Subscriber subscriber;
+
+ public SyncSetInputFromSubscriber(Subscriber subscriber, SubscriberEventHandler handler) {
+ super(handler);
+ this.subscriber = subscriber;
+ }
+
+ public void disconnect() {
+ }
+
+ public Subscriber getSubscriber() {
+ return subscriber;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.ui.sync.views.SyncSetInput#fetchInput(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void fetchInput(IProgressMonitor monitor) throws TeamException {
+ // don't calculate changes. The SubscriberEventHandler will fetch the
+ // input in a job and update this sync set when the changes are
+ // calculated.
+ }
+
+ /**
+ * Handle an error that occurred while populating the receiver's set.
+ * The <code>ITeamStatus</code> includes the resource for which the
+ * error occurred.
+ * This error is propogated to any set listeners.
+ * @param status the error status
+ */
+ public void handleError(ITeamStatus status) {
+ getSyncSet().addError(status);
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSyncSet.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSyncSet.java
new file mode 100644
index 000000000..3d84b4532
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/SyncSetInputFromSyncSet.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.team.core.ITeamStatus;
+import org.eclipse.team.core.synchronize.*;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * Ths class uses the contents of one sync set as the input of another.
+ */
+public class SyncSetInputFromSyncSet extends SyncSetInput implements ISyncInfoSetChangeListener {
+
+ SubscriberSyncInfoSet inputSyncSet;
+
+ public SyncSetInputFromSyncSet(SubscriberSyncInfoSet set, SubscriberEventHandler handler) {
+ super(handler);
+ this.inputSyncSet = set;
+ inputSyncSet.addSyncSetChangedListener(this);
+ }
+
+ public SyncInfoSet getInputSyncSet() {
+ return inputSyncSet;
+ }
+
+ public void disconnect() {
+ if (inputSyncSet == null) return;
+ inputSyncSet.removeSyncSetChangedListener(this);
+ inputSyncSet = null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.AbstractSyncSet#initialize(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void fetchInput(IProgressMonitor monitor) {
+ if (inputSyncSet == null) return;
+ SyncInfo[] infos = inputSyncSet.getSyncInfos();
+ for (int i = 0; i < infos.length; i++) {
+ collect(infos[i], monitor);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.ccvs.syncviews.views.ISyncSetChangedListener#syncSetChanged(org.eclipse.team.ccvs.syncviews.views.SyncSetChangedEvent)
+ */
+ public void syncInfoChanged(ISyncInfoSetChangeEvent event, IProgressMonitor monitor) {
+ SyncInfoSet syncSet = getSyncSet();
+ try {
+ syncSet.beginInput();
+ monitor.beginTask(null, 105);
+ syncSetChanged(event.getChangedResources(), Policy.subMonitorFor(monitor, 50));
+ syncSetChanged(event.getAddedResources(), Policy.subMonitorFor(monitor, 50));
+ remove(event.getRemovedResources());
+ } finally {
+ syncSet.endInput(Policy.subMonitorFor(monitor, 5));
+ }
+ }
+
+ private void syncSetChanged(SyncInfo[] infos, IProgressMonitor monitor) {
+ for (int i = 0; i < infos.length; i++) {
+ collect(infos[i], monitor);
+ }
+ }
+
+ private void remove(IResource[] resources) {
+ for (int i = 0; i < resources.length; i++) {
+ remove(resources[i]);
+ }
+ }
+
+ public void reset() {
+ inputSyncSet.connect(this);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.subscribers.ISyncInfoSetChangeListener#syncInfoSetReset(org.eclipse.team.core.subscribers.SyncInfoSet, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void syncInfoSetReset(SyncInfoSet set, IProgressMonitor monitor) {
+ SyncInfoSet syncSet = getSyncSet();
+ try {
+ syncSet.beginInput();
+ monitor.beginTask(null, 100);
+ syncSet.clear();
+ fetchInput(Policy.subMonitorFor(monitor, 95));
+ } finally {
+ syncSet.endInput(Policy.subMonitorFor(monitor, 5));
+ monitor.done();
+ }
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.core.subscribers.ISyncInfoSetChangeListener#syncInfoSetError(org.eclipse.team.core.subscribers.SyncInfoSet, org.eclipse.team.core.ITeamStatus[], org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void syncInfoSetErrors(SyncInfoSet set, ITeamStatus[] errors, IProgressMonitor monitor) {
+ SubscriberSyncInfoSet syncSet = getSyncSet();
+ try {
+ syncSet.beginInput();
+ for (int i = 0; i < errors.length; i++) {
+ ITeamStatus status = errors[i];
+ syncSet.addError(status);
+ }
+ } finally {
+ syncSet.endInput(monitor);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/WorkingSetSyncSetInput.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/WorkingSetSyncSetInput.java
new file mode 100644
index 000000000..7cb0fdbcb
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/WorkingSetSyncSetInput.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.subscribers.Subscriber;
+
+public class WorkingSetSyncSetInput extends SyncSetInputFromSyncSet {
+
+ private SyncInfoWorkingSetFilter workingSetFilter = new SyncInfoWorkingSetFilter();
+
+ public WorkingSetSyncSetInput(SubscriberSyncInfoSet set, SubscriberEventHandler handler) {
+ super(set, handler);
+ setFilter(workingSetFilter);
+ }
+
+ public void setWorkingSet(IResource[] resources) {
+ workingSetFilter.setWorkingSet(resources);
+ }
+
+ public IResource[] getWorkingSet() {
+ return workingSetFilter.getWorkingSet();
+ }
+
+ public IResource[] roots(Subscriber subscriber) {
+ return workingSetFilter.getRoots(subscriber);
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/DescendantResourceVariantTree.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/DescendantResourceVariantTree.java
new file mode 100644
index 000000000..e3653f968
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/DescendantResourceVariantTree.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers.caches;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.TeamException;
+
+/**
+ * A <code>ResourceVariantTree</code> that optimizes the memory footprint
+ * of a remote resource variant tree by only storing those bytes that
+ * differ from a base resource variant tree. This class should only be used
+ * for cases where the base and remote are on the same line-of-descent.
+ * For example, when the remote tree represents the current state of a branch
+ * and the base represents the state of the same branch when the local workspace
+ * as last refreshed.
+ * <p>
+ * This class also contains the logic that allows subclasses to determine if
+ * bytes stored in the remote tree are on a different line-of-descent than the base.
+ * This is necessary because it is possible for the base tree to change in ways that
+ * invalidate the stored remote variants. For example, if the local resources are moved
+ * from the main trunck to a branch, any cached remote resource variants would be stale.
+
+ */
+public abstract class DescendantResourceVariantTree extends ResourceVariantTree {
+ ResourceVariantTree baseCache, remoteCache;
+
+ public DescendantResourceVariantTree(ResourceVariantTree baseCache, ResourceVariantTree remoteCache) {
+ this.baseCache = baseCache;
+ this.remoteCache = remoteCache;
+ }
+
+ /**
+ * This method will dispose the remote cache but not the base cache.
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#dispose()
+ */
+ public void dispose() {
+ remoteCache.dispose();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#getBytes(org.eclipse.core.resources.IResource)
+ */
+ public byte[] getBytes(IResource resource) throws TeamException {
+ byte[] remoteBytes = remoteCache.getBytes(resource);
+ byte[] baseBytes = baseCache.getBytes(resource);
+ if (baseBytes == null) {
+ // There is no base so use the remote bytes
+ return remoteBytes;
+ }
+ if (remoteBytes == null) {
+ if (isVariantKnown(resource)) {
+ // The remote is known to not exist
+ // TODO: The check for NO_REMOTE does not take into consideration the line-of-descent
+ return remoteBytes;
+ } else {
+ // The remote was either never queried or was the same as the base.
+ // In either of these cases, the base bytes are used.
+ return baseBytes;
+ }
+ }
+ if (isDescendant(resource, baseBytes, remoteBytes)) {
+ // Only use the remote bytes if they are later on the same line-of-descent as the base
+ return remoteBytes;
+ }
+ // Use the base sbytes since the remote bytes must be stale (i.e. are
+ // not on the same line-of-descent
+ return baseBytes;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#setBytes(org.eclipse.core.resources.IResource, byte[])
+ */
+ public boolean setBytes(IResource resource, byte[] bytes) throws TeamException {
+ byte[] baseBytes = baseCache.getBytes(resource);
+ if (baseBytes != null && equals(baseBytes, bytes)) {
+ // Remove the existing bytes so the base will be used (thus saving space)
+ return remoteCache.removeBytes(resource, IResource.DEPTH_ZERO);
+ } else {
+ return remoteCache.setBytes(resource, bytes);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#removeBytes(org.eclipse.core.resources.IResource, int)
+ */
+ public boolean removeBytes(IResource resource, int depth) throws TeamException {
+ return remoteCache.removeBytes(resource, depth);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#isVariantKnown(org.eclipse.core.resources.IResource)
+ */
+ public boolean isVariantKnown(IResource resource) throws TeamException {
+ return remoteCache.isVariantKnown(resource);
+ }
+
+ /**
+ * This method indicates whether the remote bytes are a later revision or version
+ * on the same line-of-descent as the base. A line of descent may be a branch or a fork
+ * (depending on the terminology used by the versioing server). If this method returns
+ * <code>false</code> then the remote bytes will be ignored by this tree.
+ * @param resource the local resource
+ * @param baseBytes the base bytes for the local resoource
+ * @param remoteBytes the remote bytes for the local resoource
+ * @return whether the remote bytes are later on the same line-of-descent as the base bytes
+ */
+ protected abstract boolean isDescendant(IResource resource, byte[] baseBytes, byte[] remoteBytes) throws TeamException;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#setVariantDoesNotExist(org.eclipse.core.resources.IResource)
+ */
+ public boolean setVariantDoesNotExist(IResource resource) throws TeamException {
+ return remoteCache.setVariantDoesNotExist(resource);
+ }
+
+ /**
+ * Return the base tree from which the remote is descendant.
+ * @return Returns the base tree.
+ */
+ protected ResourceVariantTree getBaseTree() {
+ return baseCache;
+ }
+
+ /**
+ * Return the remote tree which contains bytes only for the resource variants
+ * that differ from those in the base tree.
+ * @return Returns the remote tree.
+ */
+ protected ResourceVariantTree getRemoteTree() {
+ return remoteCache;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#members(org.eclipse.core.resources.IResource)
+ */
+ public IResource[] members(IResource resource) throws TeamException {
+ return getRemoteTree().members(resource);
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/PersistantResourceVariantTree.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/PersistantResourceVariantTree.java
new file mode 100644
index 000000000..d998c9cd7
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/PersistantResourceVariantTree.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers.caches;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.internal.core.Assert;
+
+/**
+ * A <code>ResourceVariantTree</code> that caches the variant bytes using
+ * the <code>org.eclipse.core.resources.ISynchronizer</code> so that
+ * the tree is cached accross workbench invocations.
+ */
+public class PersistantResourceVariantTree extends ResourceVariantTree {
+
+ private static final byte[] NO_REMOTE = new byte[0];
+
+ private QualifiedName syncName;
+
+ /**
+ * Create a persistant tree that uses the given qualified name
+ * as the key in the <code>org.eclipse.core.resources.ISynchronizer</code>.
+ * It must be unique and should use the plugin as the local name
+ * and a unique id within the plugin as the qualifier name.
+ * @param name the key used in the Core synchronizer
+ */
+ public PersistantResourceVariantTree(QualifiedName name) {
+ syncName = name;
+ getSynchronizer().add(syncName);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#dispose()
+ */
+ public void dispose() {
+ getSynchronizer().remove(getSyncName());
+ }
+
+ /**
+ * Convenience method that returns the Core <code>ISynchronizer</code>.
+ */
+ protected ISynchronizer getSynchronizer() {
+ return ResourcesPlugin.getWorkspace().getSynchronizer();
+ }
+
+ /**
+ * Return the qualified name that uniquely identifies this tree.
+ * @return the qwualified name that uniquely identifies this tree.
+ */
+ public QualifiedName getSyncName() {
+ return syncName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#getBytes(org.eclipse.core.resources.IResource)
+ */
+ public byte[] getBytes(IResource resource) throws TeamException {
+ byte[] syncBytes = internalGetSyncBytes(resource);
+ if (syncBytes != null && equals(syncBytes, NO_REMOTE)) {
+ // If it is known that there is no remote, return null
+ return null;
+ }
+ return syncBytes;
+ }
+
+ private byte[] internalGetSyncBytes(IResource resource) throws TeamException {
+ try {
+ return getSynchronizer().getSyncInfo(getSyncName(), resource);
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#setBytes(org.eclipse.core.resources.IResource, byte[])
+ */
+ public boolean setBytes(IResource resource, byte[] bytes) throws TeamException {
+ Assert.isNotNull(bytes);
+ byte[] oldBytes = internalGetSyncBytes(resource);
+ if (oldBytes != null && equals(oldBytes, bytes)) return false;
+ try {
+ getSynchronizer().setSyncInfo(getSyncName(), resource, bytes);
+ return true;
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#removeBytes(org.eclipse.core.resources.IResource, int)
+ */
+ public boolean removeBytes(IResource resource, int depth) throws TeamException {
+ if (resource.exists() || resource.isPhantom()) {
+ try {
+ if (depth != IResource.DEPTH_ZERO || internalGetSyncBytes(resource) != null) {
+ getSynchronizer().flushSyncInfo(getSyncName(), resource, depth);
+ return true;
+ }
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#isVariantKnown(org.eclipse.core.resources.IResource)
+ */
+ public boolean isVariantKnown(IResource resource) throws TeamException {
+ return internalGetSyncBytes(resource) != null;
+ }
+
+ /**
+ * This method should be invoked by a client to indicate that it is known that
+ * there is no remote resource associated with the local resource. After this method
+ * is invoked, <code>isRemoteKnown(resource)</code> will return <code>true</code> and
+ * <code>getSyncBytes(resource)</code> will return <code>null</code>.
+ * @return <code>true</code> if this changes the remote sync bytes
+ */
+ public boolean setVariantDoesNotExist(IResource resource) throws TeamException {
+ return setBytes(resource, NO_REMOTE);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#members(org.eclipse.core.resources.IResource)
+ */
+ public IResource[] members(IResource resource) throws TeamException {
+ if(resource.getType() == IResource.FILE) {
+ return new IResource[0];
+ }
+ try {
+ // Filter and return only resources that have sync bytes in the cache.
+ IResource[] members = ((IContainer)resource).members(true /* include phantoms */);
+ List filteredMembers = new ArrayList(members.length);
+ for (int i = 0; i < members.length; i++) {
+ IResource member = members[i];
+ if(getBytes(member) != null) {
+ filteredMembers.add(member);
+ }
+ }
+ return (IResource[]) filteredMembers.toArray(new IResource[filteredMembers.size()]);
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTree.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTree.java
new file mode 100644
index 000000000..36404a5c5
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTree.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers.caches;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.TeamException;
+
+/**
+ * The purpose of a <code>ResourceVariantTree</code> is to support the caching of
+ * the synchronization bytes for the resource variants that represent
+ * a resource line-up of interest such as a version, baseline or branch. The
+ * cache stores bytes in order to minimize the memory footprint of the tree. It is the
+ * reponsibility of the client of this API to cache enough bytes to meaningfully identify
+ * a resource variant (and possibly create an {@link IResourceVariant} handle from them).
+ * <p>
+ * The bytes for
+ * a resource variant are accessed using the local handle that corresponds to the
+ * resource variant (using the <code>getSyncInfo</code> method).
+ * The potential children of a resource variant are also accessed
+ * by using the local handle that corresponds to the resource variant
+ * (using the <code>members</code> method).
+ * TODO: Does the isRemoteKnown/setRemoteDoesNotExist make sense?
+ */
+public abstract class ResourceVariantTree {
+
+ /**
+ * Dispose of any cached sync bytes when this cache is no longer needed.
+ */
+ public abstract void dispose();
+
+ /**
+ * Return the bytes for the variant corresponding the given local resource.
+ * A return value of <code>null</code> can mean either that the
+ * variant has never been fetched or that it doesn't exist. The method
+ * <code>isVariantKnown(IResource)</code> should be used to differentiate
+ * these two cases.
+ * @param resource the local resource
+ * @return the bytes that represent the resource's variant
+ * @throws TeamException
+ */
+ public abstract byte[] getBytes(IResource resource) throws TeamException;
+
+ /**
+ * Set the bytes for the variant corresponding the given local resource.
+ * The bytes should never be
+ * <code>null</code>. If it is known that the remote does not exist,
+ * <code>setVariantDoesNotExist(IResource)</code> should be used instead. If the sync
+ * bytes for the remote are stale and should be removed, <code>removeBytes()</code>
+ * should be called.
+ * @param resource the local resource
+ * @param bytes the bytes that represent the resource's variant
+ * @return <code>true</code> if the bytes changed
+ * @throws TeamException
+ */
+ public abstract boolean setBytes(IResource resource, byte[] bytes) throws TeamException;
+
+ /**
+ * Remove the bytes from the tree for the resource variants corresponding to the
+ * local resources that are descendants of the giben locla resource to the given depth.
+ * After the bytes are removed, the operation <code>isVariantKnown(resource)</code>
+ * will return <code>false</code>
+ * and <code>getBytes(resource)</code> will return <code>null</code> for the
+ * affected resources.
+ * @param resource the local resource
+ * @parem depth the depth of the operation (one of <code>IResource.DEPTH_ZERO</code>,
+ * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
+ * @return <code>true</code> if there were bytes present which were removed
+ * @throws TeamException
+ */
+ public abstract boolean removeBytes(IResource resource, int depth) throws TeamException;
+
+ /**
+ * Return <code>true</code> if the variant associated with the given local
+ * resource has been cached. This method is useful for those cases when
+ * there are no bytes for a resource variant and the client wants to
+ * know if this means that the remote does exist (i.e. this method returns
+ * <code>true</code>) or the remote has not been fetched (i.e. this method returns
+ * <code>false</code>).
+ * @param resource the local resource
+ * @return <code>true</code> if the variant associated with the given local
+ * resource has been cached.
+ * @throws TeamException
+ */
+ public abstract boolean isVariantKnown(IResource resource) throws TeamException;
+
+ /**
+ * This method should be invoked by a client to indicate that it is known that
+ * there is no variant associated with the local resource. After this method
+ * is invoked, <code>isVariantKnown(resource)</code> will return <code>true</code> and
+ * <code>getBytes(resource)</code> will return <code>null</code>.
+ * @param resource the local resource
+ * @return <code>true</code> if this changes the bytes for the variant
+ */
+ public abstract boolean setVariantDoesNotExist(IResource resource) throws TeamException;
+
+ /**
+ * Return the children of the given resource that have resource variants in this tree.
+ * @param resource the parent resource
+ * @return the members who have resource variants in this tree.
+ */
+ public abstract IResource[] members(IResource resource) throws TeamException;
+
+ /**
+ * Helper method to compare two byte arrays for equality
+ * @param syncBytes1 the first byte array or <code>null</code>
+ * @param syncBytes2 the second byte array or <code>null</code>
+ * @return whetehr the two arrays are equal (i.e. same content)
+ */
+ protected boolean equals(byte[] syncBytes1, byte[] syncBytes2) {
+ if (syncBytes1 == null) {
+ return syncBytes2 == null;
+ } else if (syncBytes2 == null) {
+ return false;
+ }
+ if (syncBytes1.length != syncBytes2.length) return false;
+ for (int i = 0; i < syncBytes1.length; i++) {
+ if (syncBytes1[i] != syncBytes2[i]) return false;
+ }
+ return true;
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTreeRefresh.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTreeRefresh.java
new file mode 100644
index 000000000..1c7395c7e
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/ResourceVariantTreeRefresh.java
@@ -0,0 +1,339 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers.caches;
+
+import java.util.*;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.synchronize.IResourceVariant;
+import org.eclipse.team.internal.core.Assert;
+import org.eclipse.team.internal.core.Policy;
+
+/**
+ * This class provides the logic for refreshing a resource variant tree.
+ * It provides the logic to traverse the local resource and variant resource trees in
+ * order to update the bytes stored in
+ * a <code>ResourceVariantTree</code>. It also accumulates and returns all local resources
+ * for which the corresponding resource variant has changed.
+ */
+public abstract class ResourceVariantTreeRefresh {
+
+ /**
+ * Refreshes the resource variant tree for the specified resources and possibly their descendants,
+ * depending on the depth. The default implementation of this method invokes
+ * <code>refresh(IResource, int, IProgressMonitor)</code> for each resource.
+ * Subclasses may override but should either invoke the above mentioned refresh or
+ * <code>collectChanges</code> in order to reconcile the resource variant tree.
+ * @param resources the resources whose variants should be refreshed
+ * @param depth the depth of the refresh (one of <code>IResource.DEPTH_ZERO</code>,
+ * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
+ * @param monitor a progress monitor
+ * @return the array of resources whose corresponding varianst have changed
+ * @throws TeamException
+ */
+ public IResource[] refresh(IResource[] resources, int depth, IProgressMonitor monitor) throws TeamException {
+ List changedResources = new ArrayList();
+ monitor.beginTask(null, 100 * resources.length);
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ IResource[] changed = refresh(resource, depth, Policy.subMonitorFor(monitor, 100));
+ changedResources.addAll(Arrays.asList(changed));
+ }
+ monitor.done();
+ if (changedResources == null) return new IResource[0];
+ return (IResource[]) changedResources.toArray(new IResource[changedResources.size()]);
+ }
+
+ /**
+ * Helper method invoked from <code>refresh(IResource[], int, IProgressMonitor monitor)</code>
+ * for each resource. The default implementation performs the following steps:
+ * <ol>
+ * <li>obtaine the scheduling rule for the resource
+ * as returned from <code>getSchedulingRule(IResource)</code>.
+ * <li>get the resource variant handle corresponding to the local resource by calling
+ * <code>getRemoteTree</code>.
+ * <li>pass the local resource and the resource variant handle to <code>collectChanges</code>
+ * </ol>
+ * Subclasses may override but should perform roughly the same steps.
+ * @param resource the resoure being refreshed
+ * @param depth the depth of the refresh (one of <code>IResource.DEPTH_ZERO</code>,
+ * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
+ * @param monitor a progress monitor
+ * @return the resource's whose variants have changed
+ * @throws TeamException
+ */
+ protected IResource[] refresh(IResource resource, int depth, IProgressMonitor monitor) throws TeamException {
+ IResource[] changedResources = null;
+ monitor.beginTask(null, 100);
+ ISchedulingRule rule = getSchedulingRule(resource);
+ try {
+ Platform.getJobManager().beginRule(rule, monitor);
+ if (!resource.getProject().isAccessible()) {
+ // The project is closed so silently skip it
+ return new IResource[0];
+ }
+
+ monitor.setTaskName(Policy.bind("SynchronizationCacheRefreshOperation.0", resource.getFullPath().makeRelative().toString())); //$NON-NLS-1$
+
+ // build the remote tree only if an initial tree hasn't been provided
+ IResourceVariant tree = fetchVariant(resource, depth, Policy.subMonitorFor(monitor, 70));
+
+ // update the known remote handles
+ IProgressMonitor sub = Policy.infiniteSubMonitorFor(monitor, 30);
+ try {
+ sub.beginTask(null, 64);
+ changedResources = collectChanges(resource, tree, depth, sub);
+ } finally {
+ sub.done();
+ }
+ } finally {
+ Platform.getJobManager().endRule(rule);
+ monitor.done();
+ }
+ if (changedResources == null) return new IResource[0];
+ return changedResources;
+ }
+
+ /**
+ * Collect the changes in the remote tree to the specified depth.
+ * @param local the local resource being refreshed
+ * @param remote the corresponding resource variant
+ * @param depth the depth of the refresh (one of <code>IResource.DEPTH_ZERO</code>,
+ * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
+ * @param monitor a progress monitor
+ * @return the resource's whose variants have changed
+ * @throws TeamException
+ */
+ protected IResource[] collectChanges(IResource local, IResourceVariant remote, int depth, IProgressMonitor monitor) throws TeamException {
+ List changedResources = new ArrayList();
+ collectChanges(local, remote, changedResources, depth, monitor);
+ return (IResource[]) changedResources.toArray(new IResource[changedResources.size()]);
+ }
+
+ /**
+ * Returns the resource variant tree that is being refreshed.
+ * @return the resource variant tree that is being refreshed.
+ */
+ protected abstract ResourceVariantTree getResourceVariantTree();
+
+ /**
+ * Get the bytes to be stored in the <code>ResourceVariantTree</code>
+ * from the given resource variant.
+ * @param local the local resource
+ * @param remote the corresponding resource variant handle
+ * @return the bytes for the resource variant.
+ */
+ protected abstract byte[] getBytes(IResource local, IResourceVariant remote) throws TeamException;
+
+ /**
+ * Fetch the members of the given resource variant handle. This method may
+ * return members that were fetched when <code>getRemoteTree</code> was called or
+ * may fetch the children directly.
+ * @param variant the resource variant
+ * @param progress a progress monitor
+ * @return the members of the resource variant.
+ */
+ protected abstract IResourceVariant[] fetchMembers(IResourceVariant variant, IProgressMonitor progress) throws TeamException;
+
+ /**
+ * Returns the members of the local resource. This may include all the members of
+ * the local resource or a subset that is of ineterest to the implementor.
+ * @param parent the local resource
+ * @return the members of the local resource
+ */
+ protected abstract IResource[] members(IResource parent) throws TeamException;
+
+ /**
+ * Fetch the resource variant corresponding to the given resource.
+ * The depth
+ * parameter indicates the depth of the refresh operation and also indicates the
+ * depth to which the resource variant's desendants will be traversed.
+ * This method may prefetch the descendants to the provided depth
+ * or may just return the variant handle corresponding to the given
+ * local resource, in which case
+ * the descendant variants will be fetched by <code>fecthMembers(IResourceVariant, IProgressMonitor)</code>.
+ * @param resource the local resource
+ * @param depth the depth of the refresh (one of <code>IResource.DEPTH_ZERO</code>,
+ * <code>IResource.DEPTH_ONE</code>, or <code>IResource.DEPTH_INFINITE</code>)
+ * @param monitor a progress monitor
+ * @return the resource variant corresponding to the given local resource
+ */
+ protected abstract IResourceVariant fetchVariant(IResource resource, int depth, IProgressMonitor monitor) throws TeamException;
+
+ /**
+ * Return the scheduling rule that should be obtained for the given resource.
+ * This method is invoked from <code>refresh(IResource, int, IProgressMonitor)</code>.
+ * By default, the resource's project is returned. Subclasses may override.
+ * @param resource the resource being refreshed
+ * @return a scheduling rule or <code>null</code>
+ */
+ protected ISchedulingRule getSchedulingRule(IResource resource) {
+ return resource.getProject();
+ }
+
+ private void collectChanges(IResource local, IResourceVariant remote, Collection changedResources, int depth, IProgressMonitor monitor) throws TeamException {
+ ResourceVariantTree cache = getResourceVariantTree();
+ byte[] newRemoteBytes = getBytes(local, remote);
+ boolean changed;
+ if (newRemoteBytes == null) {
+ changed = cache.setVariantDoesNotExist(local);
+ } else {
+ changed = cache.setBytes(local, newRemoteBytes);
+ }
+ if (changed) {
+ changedResources.add(local);
+ }
+ if (depth == IResource.DEPTH_ZERO) return;
+ Map children = mergedMembers(local, remote, monitor);
+ for (Iterator it = children.keySet().iterator(); it.hasNext();) {
+ IResource localChild = (IResource) it.next();
+ IResourceVariant remoteChild = (IResourceVariant)children.get(localChild);
+ collectChanges(localChild, remoteChild, changedResources,
+ depth == IResource.DEPTH_INFINITE ? IResource.DEPTH_INFINITE : IResource.DEPTH_ZERO,
+ monitor);
+ }
+
+ removeStaleBytes(local, children, changedResources);
+ }
+
+ private void removeStaleBytes(IResource local, Map children, Collection changedResources) throws TeamException {
+ // Look for resources that have sync bytes but are not in the resources we care about
+ ResourceVariantTree cache = getResourceVariantTree();
+ IResource[] resources = getChildrenWithBytes(local);
+ for (int i = 0; i < resources.length; i++) {
+ IResource resource = resources[i];
+ if (!children.containsKey(resource)) {
+ // These sync bytes are stale. Purge them
+ cache.removeBytes(resource, IResource.DEPTH_INFINITE);
+ changedResources.add(resource);
+ }
+ }
+ }
+
+ /*
+ * Return all the children of the local resource, including phantoms, that have bytes
+ * associated with them in the resource varant tree of this operation.
+ * @param local the local resource
+ * @return all children that have bytes stored in the tree.
+ * @throws TeamException
+ */
+ private IResource[] getChildrenWithBytes(IResource local) throws TeamException {
+ try {
+ if (local.getType() != IResource.FILE && (local.exists() || local.isPhantom())) {
+ IResource[] allChildren = ((IContainer)local).members(true /* include phantoms */);
+ List childrenWithSyncBytes = new ArrayList();
+ for (int i = 0; i < allChildren.length; i++) {
+ IResource resource = allChildren[i];
+ if (getResourceVariantTree().getBytes(resource) != null) {
+ childrenWithSyncBytes.add(resource);
+ }
+ }
+ return (IResource[]) childrenWithSyncBytes.toArray(
+ new IResource[childrenWithSyncBytes.size()]);
+ }
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ return new IResource[0];
+ }
+
+ private Map mergedMembers(IResource local, IResourceVariant remote, IProgressMonitor progress) throws TeamException {
+
+ // {IResource -> IRemoteResource}
+ Map mergedResources = new HashMap();
+
+ IResourceVariant[] remoteChildren;
+ if (remote == null) {
+ remoteChildren = new IResourceVariant[0];
+ } else {
+ remoteChildren = fetchMembers(remote, progress);
+ }
+
+
+ IResource[] localChildren = members(local);
+
+ if (remoteChildren.length > 0 || localChildren.length > 0) {
+ Set allSet = new HashSet(20);
+ Map localSet = null;
+ Map remoteSet = null;
+
+ if (localChildren.length > 0) {
+ localSet = new HashMap(10);
+ for (int i = 0; i < localChildren.length; i++) {
+ IResource localChild = localChildren[i];
+ String name = localChild.getName();
+ localSet.put(name, localChild);
+ allSet.add(name);
+ }
+ }
+
+ if (remoteChildren.length > 0) {
+ remoteSet = new HashMap(10);
+ for (int i = 0; i < remoteChildren.length; i++) {
+ IResourceVariant remoteChild = remoteChildren[i];
+ String name = remoteChild.getName();
+ remoteSet.put(name, remoteChild);
+ allSet.add(name);
+ }
+ }
+
+ Iterator e = allSet.iterator();
+ while (e.hasNext()) {
+ String keyChildName = (String) e.next();
+
+ if (progress != null) {
+ if (progress.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ // XXX show some progress?
+ }
+
+ IResource localChild =
+ localSet != null ? (IResource) localSet.get(keyChildName) : null;
+
+ IResourceVariant remoteChild =
+ remoteSet != null ? (IResourceVariant) remoteSet.get(keyChildName) : null;
+
+ if (localChild == null) {
+ // there has to be a remote resource available if we got this far
+ Assert.isTrue(remoteChild != null);
+ boolean isContainer = remoteChild.isContainer();
+ localChild = getResourceChild(local /* parent */, keyChildName, isContainer);
+ }
+ mergedResources.put(localChild, remoteChild);
+ }
+ }
+ return mergedResources;
+ }
+
+ /*
+ * Create a local resource handle for a resource variant whose
+ * corresponding local resource does not exist.
+ * @param parent the local parent
+ * @param childName the name of the local resource
+ * @param isContainer the type of resource (file or folder)
+ * @return a local resource handle
+ */
+ private IResource getResourceChild(IResource parent, String childName, boolean isContainer) {
+ if (parent.getType() == IResource.FILE) {
+ return null;
+ }
+ if (isContainer) {
+ return ((IContainer) parent).getFolder(new Path(childName));
+ } else {
+ return ((IContainer) parent).getFile(new Path(childName));
+ }
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SessionResourceVariantTree.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SessionResourceVariantTree.java
new file mode 100644
index 000000000..59dacc2b8
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SessionResourceVariantTree.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers.caches;
+
+import java.util.*;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.internal.core.Assert;
+
+/**
+ * A <code>ResourceVariantTree</code> that caches the variant bytes in memory
+ * and does not persist them over workbench invocations.
+ */
+public class SessionResourceVariantTree extends ResourceVariantTree {
+
+ private static final byte[] NO_REMOTE = new byte[0];
+
+ private Map syncBytesCache = new HashMap();
+ private Map membersCache = new HashMap();
+
+ private Map getSyncBytesCache() {
+ return syncBytesCache;
+ }
+
+ private byte[] internalGetSyncBytes(IResource resource) {
+ return (byte[])getSyncBytesCache().get(resource);
+ }
+
+ private void internalAddToParent(IResource resource) {
+ IContainer parent = resource.getParent();
+ if (parent == null) return;
+ List members = (List)membersCache.get(parent);
+ if (members == null) {
+ members = new ArrayList();
+ membersCache.put(parent, members);
+ }
+ members.add(resource);
+ }
+
+ private void internalSetSyncInfo(IResource resource, byte[] bytes) {
+ getSyncBytesCache().put(resource, bytes);
+ internalAddToParent(resource);
+ }
+
+ private void internalRemoveFromParent(IResource resource) {
+ IContainer parent = resource.getParent();
+ List members = (List)membersCache.get(parent);
+ if (members != null) {
+ members.remove(resource);
+ if (members.isEmpty()) {
+ membersCache.remove(parent);
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#dispose()
+ */
+ public void dispose() {
+ syncBytesCache.clear();
+ membersCache.clear();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#getBytes(org.eclipse.core.resources.IResource)
+ */
+ public byte[] getBytes(IResource resource) throws TeamException {
+ byte[] syncBytes = internalGetSyncBytes(resource);
+ if (syncBytes != null && equals(syncBytes, NO_REMOTE)) {
+ // If it is known that there is no remote, return null
+ return null;
+ }
+ return syncBytes;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#setBytes(org.eclipse.core.resources.IResource, byte[])
+ */
+ public boolean setBytes(IResource resource, byte[] bytes) throws TeamException {
+ Assert.isNotNull(bytes);
+ byte[] oldBytes = internalGetSyncBytes(resource);
+ if (oldBytes != null && equals(oldBytes, bytes)) return false;
+ internalSetSyncInfo(resource, bytes);
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#removeBytes(org.eclipse.core.resources.IResource, int)
+ */
+ public boolean removeBytes(IResource resource, int depth) throws TeamException {
+ if (getSyncBytesCache().containsKey(resource)) {
+ if (depth != IResource.DEPTH_ZERO) {
+ IResource[] members = members(resource);
+ for (int i = 0; i < members.length; i++) {
+ IResource child = members[i];
+ removeBytes(child, (depth == IResource.DEPTH_INFINITE) ? IResource.DEPTH_INFINITE: IResource.DEPTH_ZERO);
+ }
+ }
+ getSyncBytesCache().remove(resource);
+ internalRemoveFromParent(resource);
+ return true;
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#members(org.eclipse.core.resources.IResource)
+ */
+ public IResource[] members(IResource resource) {
+ List members = (List)membersCache.get(resource);
+ if (members == null) {
+ return new IResource[0];
+ }
+ return (IResource[]) members.toArray(new IResource[members.size()]);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#isVariantKnown(org.eclipse.core.resources.IResource)
+ */
+ public boolean isVariantKnown(IResource resource) throws TeamException {
+ return internalGetSyncBytes(resource) != null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.team.internal.core.subscribers.caches.ResourceVariantTree#setVariantDoesNotExist(org.eclipse.core.resources.IResource)
+ */
+ public boolean setVariantDoesNotExist(IResource resource) throws TeamException {
+ return setBytes(resource, NO_REMOTE);
+ }
+
+ /**
+ * Return <code>true</code> if no bytes are contained in this tree.
+ * @return <code>true</code> if no bytes are contained in this tree.
+ */
+ public boolean isEmpty() {
+ return syncBytesCache.isEmpty();
+ }
+}
diff --git a/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SyncTreeSubscriber.java b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SyncTreeSubscriber.java
new file mode 100644
index 000000000..6b68087d9
--- /dev/null
+++ b/bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core/subscribers/caches/SyncTreeSubscriber.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.team.internal.core.subscribers.caches;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.team.core.TeamException;
+import org.eclipse.team.core.subscribers.Subscriber;
+import org.eclipse.team.core.synchronize.*;
+
+/**
+ * A specialization of Subscriber that provides some additional logic for creating
+ * <code>SyncInfo</code> from <code>IResourceVariant</code> instances.
+ * The <code>members()</code> also assumes that remote
+ * instances are stored in the <code>ISynchronizer</code>.
+ */
+public abstract class SyncTreeSubscriber extends Subscriber {
+
+ public abstract IResourceVariant getRemoteResource(IResource resource) throws TeamException;
+
+ public abstract IResourceVariant getBaseResource(IResource resource) throws TeamException;
+
+ /**
+ * Return whether the given local resource has a corresponding remote resource
+ * @param resource the local resource
+ * @return <code>true</code> if the locla resource has a corresponding remote
+ */
+ protected abstract boolean hasRemote(IResource resource) throws TeamException;
+
+ public SyncInfo getSyncInfo(IResource resource) throws TeamException {
+ if (!isSupervised(resource)) return null;
+ IResourceVariant remoteResource = getRemoteResource(resource);
+ IResourceVariant baseResource;
+ if (getResourceComparator().isThreeWay()) {
+ baseResource= getBaseResource(resource);
+ } else {
+ baseResource = null;
+ }
+ return getSyncInfo(resource, baseResource, remoteResource);
+ }
+
+ /**
+ * @return
+ */
+ public abstract IResourceVariantComparator getResourceComparator();
+
+ /**
+ * Method that creates an instance of SyncInfo for the provider local, base and remote.
+ * Can be overiden by subclasses.
+ * @param local
+ * @param base
+ * @param remote
+ * @param monitor
+ * @return
+ */
+ protected SyncInfo getSyncInfo(IResource local, IResourceVariant base, IResourceVariant remote) throws TeamException {
+ SyncInfo info = new SyncInfo(local, base, remote, this.getResourceComparator());
+ info.init();
+ return info;
+ }
+
+ public IResource[] members(IResource resource) throws TeamException {
+ if(resource.getType() == IResource.FILE) {
+ return new IResource[0];
+ }
+ try {
+ // Filter and return only phantoms associated with the remote synchronizer.
+ IResource[] members;
+ try {
+ members = ((IContainer)resource).members(true /* include phantoms */);
+ } catch (CoreException e) {
+ if (!isSupervised(resource) || e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND) {
+ // The resource is no longer supervised or doesn't exist in any form
+ // so ignore the exception and return that there are no members
+ return new IResource[0];
+ }
+ throw e;
+ }
+ List filteredMembers = new ArrayList(members.length);
+ for (int i = 0; i < members.length; i++) {
+ IResource member = members[i];
+
+ // TODO: consider that there may be several sync states on this resource. There
+ // should instead be a method to check for the existance of a set of sync types on
+ // a resource.
+ if(member.isPhantom() && !hasRemote(member)) {
+ continue;
+ }
+
+ // TODO: Is this a valid use of isSupervised
+ if (isSupervised(resource)) {
+ filteredMembers.add(member);
+ }
+ }
+ return (IResource[]) filteredMembers.toArray(new IResource[filteredMembers.size()]);
+ } catch (CoreException e) {
+ throw TeamException.asTeamException(e);
+ }
+ }
+
+}

Back to the top