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
/*******************************************************************************
* 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 java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.mapping.ResourceTraversal;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.ITeamStatus;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.mapping.ISynchronizationScope;
import org.eclipse.team.core.mapping.ISynchronizationScopeChangeListener;
import org.eclipse.team.core.subscribers.Subscriber;
import org.eclipse.team.internal.core.BackgroundEventHandler;
import org.eclipse.team.internal.core.Messages;
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.
/**
* 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 abstract class SubscriberEventHandler extends BackgroundEventHandler {
// Changes accumulated by the event handler
private List<Event> resultCache = new ArrayList<>();
private boolean started = false;
private boolean initializing = true;
private IProgressMonitor progressGroup;
private int ticks;
private final Subscriber subscriber;
private ISynchronizationScope scope;
private ISynchronizationScopeChangeListener scopeChangeListener;
Internal resource synchronization event. Can contain a result.
/**
* Internal resource synchronization event. Can contain a result.
*/
class SubscriberEvent extends ResourceEvent{
static final int REMOVAL = 1;
static final int CHANGE = 2;
static final int INITIALIZE = 3;
SubscriberEvent(IResource resource, int type, int depth) {
super(resource, type, depth);
}
@Override
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$
}
}
public ResourceTraversal asTraversal() {
return new ResourceTraversal(new IResource[] { getResource() }, getDepth(), IResource.NONE);
}
}
Create a handler. This will initialize all resources for the subscriber associated with
the set.
Params: - subscriber – the subscriber
- scope – the scope
/**
* Create a handler. This will initialize all resources for the subscriber associated with
* the set.
* @param subscriber the subscriber
* @param scope the scope
*/
public SubscriberEventHandler(Subscriber subscriber, ISynchronizationScope scope) {
super(
NLS.bind(Messages.SubscriberEventHandler_jobName, new String[] { subscriber.getName() }),
NLS.bind(Messages.SubscriberEventHandler_errors, new String[] { subscriber.getName() }));
this.subscriber = subscriber;
this.scope = scope;
scopeChangeListener = (scope1, newMappings, newTraversals) -> reset(new ResourceTraversal[0], scope1.getTraversals());
this.scope.addScopeChangeListener(scopeChangeListener);
}
The traversals of the scope have changed
Params: - oldTraversals – the old traversals
- newTraversals – the new traversals
/**
* The traversals of the scope have changed
* @param oldTraversals the old traversals
* @param newTraversals the new traversals
*/
protected synchronized void reset(ResourceTraversal[] oldTraversals, ResourceTraversal[] newTraversals) {
reset(newTraversals, SubscriberEvent.CHANGE);
}
Start the event handler by queuing events to prime the sync set input with the out-of-sync
resources of the subscriber.
/**
* 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 queuing.
// We are guaranteed to be the first since this method is synchronized.
started = true;
ResourceTraversal[] traversals = scope.getTraversals();
reset(traversals, SubscriberEvent.INITIALIZE);
initializing = false;
}
@Override
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.
/**
* Schedule the job or process the events now.
*/
@Override
public void schedule() {
Job job = getEventHandlerJob();
if (job.getState() == Job.NONE) {
if(progressGroup != null) {
job.setSystem(false);
job.setProgressGroup(progressGroup, ticks);
} else {
job.setSystem(isSystemJob());
}
}
getEventHandlerJob().schedule();
}
protected boolean isSystemJob() {
return !initializing;
}
@Override
protected void jobDone(IJobChangeEvent event) {
super.jobDone(event);
progressGroup = null;
}
Called by a client to indicate that a resource has changed and its synchronization state
should be recalculated.
Params: - resource – the changed resource
- depth – the depth of the change calculation
/**
* 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.
Params: - resource – the resource that was removed
/**
* 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.
/**
* 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) {
Policy.checkCanceled(monitor);
// handle any preemptive events before continuing
handlePreemptiveEvents(monitor);
if (resource.getType() != IResource.FILE
&& depth != IResource.DEPTH_ZERO) {
try {
IResource[] members =
getSubscriber().members(resource);
for (IResource member : members) {
collect(member, depth == IResource.DEPTH_INFINITE
? IResource.DEPTH_INFINITE
: IResource.DEPTH_ZERO, monitor);
}
} catch (TeamException e) {
// We only handle the exception if the resource's project is accessible.
// The project close delta will clean up.
if (resource.getProject().isAccessible())
handleException(e, resource, ITeamStatus.SYNC_INFO_SET_ERROR, NLS.bind(Messages.SubscriberEventHandler_8, new String[] { resource.getFullPath().toString(), e.getMessage() }));
}
}
monitor.subTask(NLS.bind(Messages.SubscriberEventHandler_2, new String[] { resource.getFullPath().toString() }));
try {
handleChange(resource);
handlePendingDispatch(monitor);
} catch (CoreException e) {
handleException(e, resource, ITeamStatus.RESOURCE_SYNC_INFO_ERROR, NLS.bind(Messages.SubscriberEventHandler_9, new String[] { resource.getFullPath().toString(), e.getMessage() }));
}
monitor.worked(1);
}
Return the subscriber associated with this event handler
Returns: the subscriber associated with this event handler
/**
* Return the subscriber associated with this event handler
* @return the subscriber associated with this event handler
*/
protected Subscriber getSubscriber() {
return subscriber;
}
The given resource has changed. Subclasses should handle
this in an appropriate fashion
Params: - resource – the resource whose state has changed
/**
* The given resource has changed. Subclasses should handle
* this in an appropriate fashion
* @param resource the resource whose state has changed
*/
protected abstract void handleChange(IResource resource) throws CoreException;
protected void handlePendingDispatch(IProgressMonitor monitor) {
if (isReadyForDispatch(false /*don't wait if queue is empty*/)) {
try {
dispatchEvents(Policy.subMonitorFor(monitor, 5));
} catch (TeamException e) {
handleException(e, null, ITeamStatus.SYNC_INFO_SET_ERROR, e.getMessage());
}
}
}
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.
The resource passed may be null.
/**
* 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.
* The resource passed may be null.
*/
protected void handleException(CoreException e, IResource resource, int code, String message) {
handleException(e);
}
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.
Params: - resource – the resources to check
- depth – the depth
- monitor –
/**
* 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 resource the resources to check
* @param depth the depth
* @param monitor
*/
protected abstract void collectAll(
IResource resource,
int depth,
IProgressMonitor monitor);
Feed the given events to the set. The appropriate method on the set is called
for each event type.
Params: - events –
/**
* Feed the given events to the set. The appropriate method on the set is called
* for each event type.
* @param events
*/
protected abstract void dispatchEvents(SubscriberEvent[] events, IProgressMonitor monitor);
Initialize all resources for the subscriber associated with the set. This will basically recalculate
all synchronization information for the subscriber.
Params: - type – can be Event.CHANGE to recalculate all states or Event.INITIALIZE to perform the
optimized recalculation if supported by the subscriber.
/**
* 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.
*/
protected void reset(ResourceTraversal[] traversals, int type) {
for (ResourceTraversal traversal : traversals) {
IResource[] resources = traversal.getResources();
for (IResource resource : resources) {
queueEvent(new SubscriberEvent(resource, type, traversal.getDepth()), false);
}
}
}
@Override
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 BackgroundEventHandler.RUNNABLE_EVENT :
executeRunnable(event, monitor);
break;
case SubscriberEvent.REMOVAL :
queueDispatchEvent(event);
break;
case SubscriberEvent.CHANGE :
collect(
event.getResource(),
((ResourceEvent)event).getDepth(),
monitor);
break;
case SubscriberEvent.INITIALIZE :
monitor.subTask(NLS.bind(Messages.SubscriberEventHandler_2, new String[] { event.getResource().getFullPath().toString() }));
collectAll(
event.getResource(),
((ResourceEvent)event).getDepth(),
Policy.subMonitorFor(monitor, 64));
break;
}
} catch (OperationCanceledException e) {
// the job has been canceled.
// Clear the queue and propagate the cancellation through the sets.
handleCancel(e);
} catch (RuntimeException e) {
// handle the exception and keep processing
if (event.getType() == BackgroundEventHandler.RUNNABLE_EVENT ) {
handleException(new TeamException(Messages.SubscriberEventHandler_10, e));
} else {
handleException(new TeamException(Messages.SubscriberEventHandler_10, e), event.getResource(), ITeamStatus.SYNC_INFO_SET_ERROR, NLS.bind(Messages.SubscriberEventHandler_11, new String[] { event.getResource().getFullPath().toString(), e.getMessage() }));
}
}
}
Queue the event to be handle during the dispatch phase.
Params: - event – the event
/**
* Queue the event to be handle during the dispatch phase.
* @param event the event
*/
protected void queueDispatchEvent(Event event) {
resultCache.add(event);
}
Handle the cancel exception
Params: - e – the cancel exception
/**
* Handle the cancel exception
* @param e the cancel exception
*/
protected void handleCancel(OperationCanceledException e) {
resultCache.clear();
}
/*
* Execute the RunnableEvent
*/
private void executeRunnable(Event event, IProgressMonitor monitor) {
try {
// Dispatch any queued results to clear pending output events
dispatchEvents(Policy.subMonitorFor(monitor, 1));
} catch (TeamException e) {
handleException(e, null, ITeamStatus.SYNC_INFO_SET_ERROR, e.getMessage());
}
try {
((RunnableEvent)event).run(Policy.subMonitorFor(monitor, 1));
} catch (CoreException e) {
handleException(e, null, ITeamStatus.SYNC_INFO_SET_ERROR, e.getMessage());
}
}
@Override
protected boolean doDispatchEvents(IProgressMonitor monitor) {
if (!resultCache.isEmpty()) {
dispatchEvents(resultCache.toArray(new SubscriberEvent[resultCache.size()]), monitor);
resultCache.clear();
return true;
}
return false;
}
Queue up the given runnable in an event to be processed by this job
Params: - runnable –
the runnable to be run by the handler
- frontOnQueue –
the frontOnQueue flag is used to indicate that the runnable
should be placed on the front of the queue and be processed as
soon as possible
/**
* Queue up the given runnable in an event to be processed by this job
*
* @param runnable
* the runnable to be run by the handler
* @param frontOnQueue
* the frontOnQueue flag is used to indicate that the runnable
* should be placed on the front of the queue and be processed as
* soon as possible
*/
public void run(IWorkspaceRunnable runnable, boolean frontOnQueue) {
queueEvent(new RunnableEvent(runnable, frontOnQueue), frontOnQueue);
}
public void setProgressGroupHint(IProgressMonitor progressGroup, int ticks) {
this.progressGroup = progressGroup;
this.ticks = ticks;
}
protected void handlePreemptiveEvents(IProgressMonitor monitor) {
Event event = peek();
if (event instanceof RunnableEvent && ((RunnableEvent)event).isPreemtive()) {
executeRunnable(nextElement(), monitor);
}
}
Return the scope of this event handler. The scope is
used to determine the resources that are processed by the handler
Returns: the scope of this event handler
/**
* Return the scope of this event handler. The scope is
* used to determine the resources that are processed by the handler
* @return the scope of this event handler
*/
protected ISynchronizationScope getScope() {
return scope;
}
@Override
public void shutdown() {
super.shutdown();
scope.removeScopeChangeListener(scopeChangeListener);
}
}