diff options
author | Jean Michel-Lemieux | 2004-02-25 20:45:35 +0000 |
---|---|---|
committer | Jean Michel-Lemieux | 2004-02-25 20:45:35 +0000 |
commit | 611f26b53dc8c8e03019aa05a42e7ed40ad2fa51 (patch) | |
tree | da1a682b00dda95024364683fd6e75f19c4505fe /bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core | |
parent | 4a1ca77df6f4238416a8715a8c35819611d5992a (diff) | |
download | eclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.tar.gz eclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.tar.xz eclipse.platform.team-611f26b53dc8c8e03019aa05a42e7ed40ad2fa51.zip |
SyncView API released to HEAD.
Diffstat (limited to 'bundles/org.eclipse.team.core/src/org/eclipse/team/internal/core')
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); + } + } + +} |