blob: 04b85e69fabb9388291865ff337ee05216d2b62c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.team.internal.core.subscribers;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.team.core.subscribers.ISubscriberChangeEvent;
import org.eclipse.team.core.subscribers.ISubscriberChangeListener;
import org.eclipse.team.core.subscribers.Subscriber;
/**
* This class acts as a superclass for any class that is collecting subscriber
* resources. It provides functionality that listens to resource deltas and
* subscriber change events in order to determine when the state of resources
* that are supervised by a subscriber may have changed.
*/
public abstract class SubscriberResourceCollector implements IResourceChangeListener, ISubscriberChangeListener {
Subscriber subscriber;
/**
* Create the collector and register it as a listener with the workspace
* and the subscriber.
* @param subscriber the subscriber to be associated with this collector
*/
public SubscriberResourceCollector(Subscriber subscriber) {
Assert.isNotNull(subscriber);
this.subscriber = subscriber;
ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
subscriber.addListener(this);
}
/**
* Returns the <code>Subscriber</code> associated with this collector.
*
* @return the <code>Subscriber</code> associated with this collector.
*/
public Subscriber getSubscriber() {
return subscriber;
}
/**
* De-register the listeners for this collector.
*/
public void dispose() {
getSubscriber().removeListener(this);
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
}
@Override
public void subscriberResourceChanged(ISubscriberChangeEvent[] deltas) {
try {
beginInput();
IResource[] roots = getRoots();
for (int i = 0; i < deltas.length; i++) {
switch (deltas[i].getFlags()) {
case ISubscriberChangeEvent.SYNC_CHANGED :
if (isAllRootsIncluded() || isDescendantOfRoot(deltas[i].getResource(), roots)) {
change(deltas[i].getResource(), IResource.DEPTH_ZERO);
}
break;
case ISubscriberChangeEvent.ROOT_REMOVED :
remove(deltas[i].getResource());
break;
case ISubscriberChangeEvent.ROOT_ADDED :
if (isAllRootsIncluded() || isDescendantOfRoot(deltas[i].getResource(), roots)) {
change(deltas[i].getResource(), IResource.DEPTH_INFINITE);
}
break;
}
}
} finally {
endInput();
}
}
/**
* This method is invoked at the beginning of a subscriber change event
* or resource delta event. The endInput method will be invoked at some point
* following this. There may be several invocations of remove or change
* in between.
*/
protected void beginInput() {
// Do nothing by default
}
/**
* The processing of the resource or subscriber delta has finished.
* Subclasses can accumulate removals and changes and handle them
* at this point to allow batched change events.
*/
protected void endInput() {
// Do nothing by default
}
@Override
public void resourceChanged(IResourceChangeEvent event) {
try {
beginInput();
processDelta(event.getDelta(), getRoots());
} finally {
endInput();
}
}
/**
* Process the resource delta and posts all necessary events to the background
* event handler.
*
* @param delta the resource delta to analyze
*/
protected void processDelta(IResourceDelta delta, IResource[] roots) {
IResource resource = delta.getResource();
int kind = delta.getKind();
if (resource.getType() == IResource.PROJECT) {
// Handle projects that should be removed from the collector
if (((kind & IResourceDelta.REMOVED) != 0) /* deleted project */
|| (delta.getFlags() & IResourceDelta.OPEN) != 0 && !((IProject) resource).isOpen() /* closed project */
|| !isAncestorOfRoot(resource, roots)) /* not within subscriber roots */ {
// If the project has any entries in the sync set, remove them
if (hasMembers(resource)) {
remove(resource);
}
}
}
boolean visitChildren = false;
if (isDescendantOfRoot(resource, roots)) {
visitChildren = true;
// If the resource has changed type, remove the old resource handle
// and add the new one
if ((delta.getFlags() & IResourceDelta.TYPE) != 0) {
remove(resource);
change(resource, IResource.DEPTH_INFINITE);
}
// Check the flags for changes the SyncSet cares about.
// Notice we don't care about MARKERS currently.
int changeFlags = delta.getFlags();
if ((changeFlags & (IResourceDelta.OPEN | IResourceDelta.CONTENT)) != 0) {
change(resource, IResource.DEPTH_ZERO);
}
// Check the kind and deal with those we care about
if ((delta.getKind() & (IResourceDelta.REMOVED | IResourceDelta.ADDED)) != 0) {
change(resource, IResource.DEPTH_ZERO);
}
}
// Handle changed children
if (visitChildren || isAncestorOfRoot(resource, roots)) {
IResourceDelta[] affectedChildren = delta.getAffectedChildren(IResourceDelta.CHANGED | IResourceDelta.REMOVED | IResourceDelta.ADDED);
for (int i = 0; i < affectedChildren.length; i++) {
processDelta(affectedChildren[i], roots);
}
}
}
/**
* Return the root resources that are to be considered by this handler.
* These may be either the subscriber roots or a set of resources that are
* contained by the subscriber's roots.
* @return the root resources that are to be considered by this handler
*/
protected IResource[] getRoots() {
return getSubscriber().roots();
}
/**
* Return whether the given resource, which is not
* within the roots of this handler, has children
* that are.
* @param resource the resource
* @return whether the resource has children that are being considered
* by this handler.
*/
protected abstract boolean hasMembers(IResource resource);
/**
* The resource is no longer of concern to the subscriber.
* Remove the resource and any of it's descendants
* from the set of resources being collected.
* @param resource the resource to be removed along with its
* descendants.
*/
protected abstract void remove(IResource resource);
/**
* The resource sync state has changed to the depth specified.
* @param resource the resource
* @param depth the depth
*/
protected abstract void change(IResource resource, int depth);
/**
* Return whether all roots of a subscriber are included or
* if the collector is only consider a subset of the resources.
* @return whether all roots of a subscriber are included
*/
protected boolean isAllRootsIncluded() {
return true;
}
private boolean isAncestorOfRoot(IResource parent, IResource[] roots) {
// Always traverse into projects in case a root was removed
if (parent.getType() == IResource.ROOT) return true;
for (int i = 0; i < roots.length; i++) {
IResource resource = roots[i];
if (parent.getFullPath().isPrefixOf(resource.getFullPath())) {
return true;
}
}
return false;
}
private boolean isDescendantOfRoot(IResource resource, IResource[] roots) {
for (int i = 0; i < roots.length; i++) {
IResource root = roots[i];
if (root.getFullPath().isPrefixOf(resource.getFullPath())) {
return true;
}
}
return false;
}
}