/******************************************************************************* * Copyright (c) 2000, 2017 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-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.resources.IWorkspaceRunnable; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.*; import org.eclipse.team.core.TeamException; /** * This class provides the infrastructure for processing/dispatching of events using a * background job. This is useful in the following situations. *
* The event handler has the following characteristics: *
processEvent
method
* which is implemented by the subclass. The implementation may choose to process events
* directly or queue events on an outgoing event queuedoDispatchEvents
method of the subclass is called at certain intervals
* to give the subclass a chance to dispatch the events in it's outgoing queue. The interval between
* the first 3 dispatches will be the shortDispatchDelay
and subsequent intervals will be
* the longDispatchDelay
. This is done to avoid constantly hammering the UI for long running
* operations.handle
* method. Accumulated errors are used to form the status that is returned when the job completes.processEvent(Event)
which may check for and handle
* cancelation by shutting down the receiver.
*
* The isReadyForDispatch()
method is used in conjunction
* with the dispatchEvents(IProgressMonitor)
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, IProgressMonitor.UNKNOWN);
IProgressMonitor subMonitor = Policy.infiniteSubMonitorFor(monitor, 90);
subMonitor.beginTask(null, 1024);
Event event;
timeOfLastDispatch = System.currentTimeMillis();
dispatchCount = 1;
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));
}
} catch (CoreException e) {
// handle exception but keep going
handleException(e);
}
}
} finally {
monitor.done();
}
return errors.getStatus();
}
/**
* Dispatch any accumulated events by invoking doDispatchEvents
* and then rest the dispatch counters.
* @param monitor a progress monitor
* @throws TeamException
*/
protected final void dispatchEvents(IProgressMonitor monitor) throws TeamException {
if (doDispatchEvents(monitor)) {
// something was dispatched so adjust dispatch count.
dispatchCount++;
}
timeOfLastDispatch = System.currentTimeMillis();
}
/**
* Notify clients of processed events. Return true
if there
* was something to dispatch and false otherwise. This is used to help
* control the frequency of dispatches (e.g. if there is a lot of dispatching
* going on, the frequency of dispatches may be reduced.
* @param monitor a progress monitor
*/
protected abstract boolean doDispatchEvents(IProgressMonitor monitor) throws TeamException;
/**
* Returns true
if processed events should be dispatched and
* false
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 true
if processed events should be dispatched and
* false
otherwise
*/
protected boolean isReadyForDispatch(boolean wait) {
// Check if the time since the last dispatch is greater than the delay.
if (isDispatchDelayExceeded())
return true;
synchronized(this) {
// If we have incoming events, process them before dispatching
if(! isQueueEmpty() || ! wait) {
return false;
}
// There are no incoming events but we want to wait a little before
// dispatching in case more events come in.
try {
wait(getDispatchWaitDelay());
} catch (InterruptedException e) {
// just continue
}
}
return isQueueEmpty() || isDispatchDelayExceeded();
}
private boolean isDispatchDelayExceeded() {
long duration = System.currentTimeMillis() - timeOfLastDispatch;
return ((dispatchCount < DISPATCH_THRESHOLD && duration >= getShortDispatchDelay()) ||
duration >= getLongDispatchDelay());
}
/**
* Return the amount of time to wait for more events before dispatching.
* @return the amount of time to wait for more events before dispatching.
*/
protected long getDispatchWaitDelay() {
return WAIT_DELAY;
}
/**
* Return the value that is used to determine how often
* the events are dispatched (i.e. how often the UI is
* updated) for the first 3 cycles. The default value is 1.5 seconds.
* After the first 3 cycles, a longer delay is used
* @return the dispatch delay used for the first 3 cycles.
*/
protected long getShortDispatchDelay() {
return DISPATCH_DELAY;
}
/**
* Return the value that is used to determine how often
* the events are dispatched (i.e. how often the UI is
* updated) after the first 3 cycles. The default value is 10 seconds.
* @return the dispatch delay used after the first 3 cycles.
*/
protected long getLongDispatchDelay() {
return LONG_DISPATCH_DELAY;
}
/**
* 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 shutdown()
method.
*
* 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
* proceessEvent
method and propagate them from the dispatchEvent
* method which is invoked periodically in order to batch outgoing events and avoid
* the UI becoming too jumpy.
*
* @param event the Event
to be processed
* @param monitor a progress monitor
*/
protected abstract void processEvent(Event event, IProgressMonitor monitor) throws CoreException;
/**
* Return the job from which the processedEvent
method is invoked.
* @return Returns the background event handling job.
*/
public Job getEventHandlerJob() {
return eventHandlerJob;
}
}