blob: 36a42c1edfd3cd3a388fe694ef89bdfd6cad158e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2012 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 - Initial API and implementation
* Stephan Wahlbrink - Fix for bug 200997.
*******************************************************************************/
package org.eclipse.core.internal.jobs;
import java.util.Map;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.*;
/**
* Internal implementation class for jobs. Clients must not implement this class
* directly. All jobs must be subclasses of the API <code>org.eclipse.core.runtime.jobs.Job</code> class.
*/
public abstract class InternalJob extends PlatformObject implements Comparable {
/**
* Job state code (value 16) indicating that a job has been removed from
* the wait queue and is about to start running. From an API point of view,
* this is the same as RUNNING.
*/
static final int ABOUT_TO_RUN = 0x10;
/**
* Job state code (value 32) indicating that a job has passed scheduling
* precondition checks and is about to be added to the wait queue. From an API point of view,
* this is the same as WAITING.
*/
static final int ABOUT_TO_SCHEDULE = 0x20;
/**
* Job state code (value 8) indicating that a job is blocked by another currently
* running job. From an API point of view, this is the same as WAITING.
*/
static final int BLOCKED = 0x08;
/**
* Job state code (value 64) indicating that a job is yielding.
* From an API point of view, this is the same as WAITING.
*/
static final int YIELDING = 0x40;
//flag mask bits
private static final int M_STATE = 0xFF;
private static final int M_SYSTEM = 0x0100;
private static final int M_USER = 0x0200;
/*
* flag on a job indicating that it was about to run, but has been canceled
*/
private static final int M_ABOUT_TO_RUN_CANCELED = 0x0400;
/*
* Flag on a job indicating that it was canceled when running. This flag
* is used to ensure that #canceling is only ever called once on a job in
* case of recursive cancelation attempts.
*/
private static final int M_RUN_CANCELED = 0x0800;
private static int nextJobNumber = 0;
protected static final JobManager manager = JobManager.getInstance();
/**
* Start time constant indicating a job should be started at
* a time in the infinite future, causing it to sleep forever.
*/
static final long T_INFINITE = Long.MAX_VALUE;
/**
* Start time constant indicating that the job has no start time.
*/
static final long T_NONE = -1;
private volatile int flags = Job.NONE;
private final int jobNumber = getNextJobNumber();
private ListenerList listeners = null;
private volatile IProgressMonitor monitor;
private String name;
/**
* The job ahead of me in a queue or list.
* @GuardedBy("manager.lock")
*/
private InternalJob next;
/**
* The job behind me in a queue or list.
* @GuardedBy("manager.lock")
*/
private InternalJob previous;
private int priority = Job.LONG;
/**
* Arbitrary properties (key,value) pairs, attached
* to a job instance by a third party.
*/
private ObjectMap properties;
/**
* Volatile because it is usually set via a Worker thread and is read via a
* client thread.
*/
private volatile IStatus result;
/**
* @GuardedBy("manager.lock")
*/
private ISchedulingRule schedulingRule;
/**
* If the job is waiting, this represents the time the job should start by.
* If this job is sleeping, this represents the time the job should wake up.
* If this job is running, this represents the delay automatic rescheduling,
* or -1 if the job should not be rescheduled.
* @GuardedBy("manager.lock")
*/
private long startTime;
/**
* Stamp added when a job is added to the wait queue. Used to ensure
* jobs in the wait queue maintain their insertion order even if they are
* removed from the wait queue temporarily while blocked
* @GuardedBy("manager.lock")
*/
private long waitQueueStamp = T_NONE;
/*
* The thread that is currently running this job
*/
private volatile Thread thread = null;
/**
* This lock will be held while performing state changes on this job. It is
* also used as a notifier used to wake up yielding jobs or waiting ThreadJobs
* when 1) a conflicting job completes and releases a scheduling rule, or 2)
* when a this job changes state.
*
* See also the lock ordering protocol explanation in JobManager's
* documentation.
*
* @GuardedBy("itself")
*/
final Object jobStateLock = new Object();
private static synchronized int getNextJobNumber() {
return nextJobNumber++;
}
protected InternalJob(String name) {
Assert.isNotNull(name);
this.name = name;
}
/* (non-Javadoc)
* @see Job#addJobListener(IJobChangeListener)
*/
protected synchronized void addJobChangeListener(IJobChangeListener listener) {
if (listeners == null)
listeners = new ListenerList(ListenerList.IDENTITY);
listeners.add(listener);
}
/**
* Adds an entry at the end of the list of which this item is the head.
* @GuardedBy("manager.lock")
*/
final void addLast(InternalJob entry) {
InternalJob last = this;
//find the end of the queue
while (last.previous != null)
last = last.previous;
//add the new entry to the end of the queue
last.previous = entry;
entry.next = last;
entry.previous = null;
}
/* (non-Javadoc)
* @see Job#belongsTo(Object)
*/
protected boolean belongsTo(Object family) {
return false;
}
/* (non-Javadoc)
* @see Job#cancel()
*/
protected boolean cancel() {
return manager.cancel(this);
}
/* (non-Javadoc)
* @see Job#canceling()
*/
protected void canceling() {
//default implementation does nothing
}
/* (on-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public final int compareTo(Object otherJob) {
return ((InternalJob) otherJob).startTime >= startTime ? 1 : -1;
}
/* (non-Javadoc)
* @see Job#done(IStatus)
*/
protected void done(IStatus endResult) {
manager.endJob(this, endResult, true);
}
/**
* Returns the job listeners that are only listening to this job. Returns
* <code>null</code> if this job has no listeners.
*/
final ListenerList getListeners() {
return listeners;
}
/* (non-Javadoc)
* @see Job#getName()
*/
protected String getName() {
return name;
}
/* (non-Javadoc)
* @see Job#getPriority()
*/
protected int getPriority() {
return priority;
}
/**
* Returns the job's progress monitor, or null if it is not running.
*/
final IProgressMonitor getProgressMonitor() {
return monitor;
}
/* (non-Javadoc)
* @see Job#getProperty
*/
protected Object getProperty(QualifiedName key) {
// thread safety: (Concurrency001 - copy on write)
Map temp = properties;
if (temp == null)
return null;
return temp.get(key);
}
/* (non-Javadoc)
* @see Job#getResult
*/
protected IStatus getResult() {
return result;
}
/* (non-Javadoc)
* @see Job#getRule
*/
protected ISchedulingRule getRule() {
return schedulingRule;
}
/**
* Returns the time that this job should be started, awakened, or
* rescheduled, depending on the current state.
* @return time in milliseconds
*/
final long getStartTime() {
return startTime;
}
/* (non-Javadoc)
* @see Job#getState()
*/
protected int getState() {
int state = flags & M_STATE;
switch (state) {
//blocked and yielding state is equivalent to waiting state for clients
case YIELDING :
case BLOCKED :
return Job.WAITING;
case ABOUT_TO_RUN :
return Job.RUNNING;
case ABOUT_TO_SCHEDULE :
return Job.WAITING;
default :
return state;
}
}
/* (non-javadoc)
* @see Job.getThread
*/
protected Thread getThread() {
return thread;
}
/**
* Returns the raw job state, including internal states no exposed as API.
*/
final int internalGetState() {
return flags & M_STATE;
}
/**
* Must be called from JobManager#setPriority
*/
final void internalSetPriority(int newPriority) {
this.priority = newPriority;
}
/**
* Must be called from JobManager#setRule
*/
final void internalSetRule(ISchedulingRule rule) {
this.schedulingRule = rule;
}
/**
* Must be called from JobManager#changeState
*/
final void internalSetState(int i) {
flags = (flags & ~M_STATE) | i;
}
/**
* Returns whether this job was canceled when it was about to run
*/
final boolean isAboutToRunCanceled() {
return (flags & M_ABOUT_TO_RUN_CANCELED) != 0;
}
/**
* Returns whether this job was canceled when it was running.
*/
final boolean isRunCanceled() {
return (flags & M_RUN_CANCELED) != 0;
}
/* (non-Javadoc)
* @see Job#isBlocking()
*/
protected boolean isBlocking() {
return manager.isBlocking(this);
}
/**
* Returns true if this job conflicts with the given job, and false otherwise.
*/
final boolean isConflicting(InternalJob otherJob) {
ISchedulingRule otherRule = otherJob.getRule();
if (schedulingRule == null || otherRule == null)
return false;
//if one of the rules is a compound rule, it must be asked the question.
if (schedulingRule.getClass() == MultiRule.class)
return schedulingRule.isConflicting(otherRule);
return otherRule.isConflicting(schedulingRule);
}
/* (non-javadoc)
* @see Job.isSystem()
*/
protected boolean isSystem() {
return (flags & M_SYSTEM) != 0;
}
/* (non-javadoc)
* @see Job.isUser()
*/
protected boolean isUser() {
return (flags & M_USER) != 0;
}
/* (non-Javadoc)
* @see Job#join()
*/
protected void join() throws InterruptedException {
manager.join(this);
}
/**
* Returns the next entry (ahead of this one) in the list, or null if there is no next entry
*/
final InternalJob next() {
return next;
}
/**
* Returns the previous entry (behind this one) in the list, or null if there is no previous entry
*/
final InternalJob previous() {
return previous;
}
/**
* Removes this entry from any list it belongs to. Returns the receiver.
*/
final InternalJob remove() {
if (next != null)
next.setPrevious(previous);
if (previous != null)
previous.setNext(next);
next = previous = null;
return this;
}
/* (non-Javadoc)
* @see Job#removeJobListener(IJobChangeListener)
*/
protected synchronized void removeJobChangeListener(IJobChangeListener listener) {
if (listeners != null) {
listeners.remove(listener);
if (listeners.isEmpty())
listeners = null;
}
}
/* (non-Javadoc)
* @see Job#run(IProgressMonitor)
*/
protected abstract IStatus run(IProgressMonitor progressMonitor);
/* (non-Javadoc)
* @see Job#schedule(long)
*/
protected void schedule(long delay) {
if (shouldSchedule())
manager.schedule(this, delay, false);
}
/**
* Sets whether this job was canceled when it was about to run
*/
final void setAboutToRunCanceled(boolean value) {
flags = value ? flags | M_ABOUT_TO_RUN_CANCELED : flags & ~M_ABOUT_TO_RUN_CANCELED;
}
/**
* Sets whether this job was canceled when it was running
*/
final void setRunCanceled(boolean value) {
flags = value ? flags | M_RUN_CANCELED : flags & ~M_RUN_CANCELED;
}
/* (non-Javadoc)
* @see Job#setName(String)
*/
protected void setName(String name) {
Assert.isNotNull(name);
this.name = name;
}
/**
* Sets the next entry in this linked list of jobs.
* @param entry
*/
final void setNext(InternalJob entry) {
this.next = entry;
}
/**
* Sets the previous entry in this linked list of jobs.
* @param entry
*/
final void setPrevious(InternalJob entry) {
this.previous = entry;
}
/* (non-Javadoc)
* @see Job#setPriority(int)
*/
protected void setPriority(int newPriority) {
switch (newPriority) {
case Job.INTERACTIVE :
case Job.SHORT :
case Job.LONG :
case Job.BUILD :
case Job.DECORATE :
manager.setPriority(this, newPriority);
break;
default :
throw new IllegalArgumentException(String.valueOf(newPriority));
}
}
/* (non-Javadoc)
* @see Job#setProgressGroup(IProgressMonitor, int)
*/
protected void setProgressGroup(IProgressMonitor group, int ticks) {
Assert.isNotNull(group);
IProgressMonitor pm = manager.createMonitor(this, group, ticks);
if (pm != null)
setProgressMonitor(pm);
}
/**
* Sets the progress monitor to use for the next execution of this job,
* or for clearing the monitor when a job completes.
* @param monitor a progress monitor
*/
final void setProgressMonitor(IProgressMonitor monitor) {
this.monitor = monitor;
}
/* (non-Javadoc)
* @see Job#setProperty(QualifiedName,Object)
*/
protected void setProperty(QualifiedName key, Object value) {
// thread safety: (Concurrency001 - copy on write)
if (value == null) {
if (properties == null)
return;
ObjectMap temp = (ObjectMap) properties.clone();
temp.remove(key);
if (temp.isEmpty())
properties = null;
else
properties = temp;
} else {
ObjectMap temp = properties;
if (temp == null)
temp = new ObjectMap(5);
else
temp = (ObjectMap) properties.clone();
temp.put(key, value);
properties = temp;
}
}
/**
* Sets or clears the result of an execution of this job.
* @param result a result status, or <code>null</code>
* @GuardedBy("manager.lock")
*/
final void setResult(IStatus result) {
this.result = result;
}
/* (non-Javadoc)
* @see Job#setRule(ISchedulingRule)
* @GuardedBy("manager.lock")
*/
protected void setRule(ISchedulingRule rule) {
manager.setRule(this, rule);
}
/**
* Sets a time to start, wake up, or schedule this job,
* depending on the current state
* @param time a time in milliseconds
* @GuardedBy("manager.lock")
*/
final void setStartTime(long time) {
startTime = time;
}
/* (non-javadoc)
* @see Job.setSystem
*/
protected void setSystem(boolean value) {
if (getState() != Job.NONE)
throw new IllegalStateException();
flags = value ? flags | M_SYSTEM : flags & ~M_SYSTEM;
}
/* (non-javadoc)
* @see Job.setThread
*/
protected void setThread(Thread thread) {
this.thread = thread;
}
/* (non-javadoc)
* @see Job.setUser
*/
protected void setUser(boolean value) {
if (getState() != Job.NONE)
throw new IllegalStateException();
flags = value ? flags | M_USER : flags & ~M_USER;
}
/* (Non-javadoc)
* @see Job#shouldSchedule
*/
protected boolean shouldSchedule() {
return true;
}
/* (non-Javadoc)
* @see Job#sleep()
*/
protected boolean sleep() {
return manager.sleep(this);
}
/* (non-Javadoc)
* @see Job#yieldRule()
*/
protected Job yieldRule(IProgressMonitor progressMonitor) {
return manager.yieldRule(this, progressMonitor);
}
/* (non-Javadoc)
* Prints a string-based representation of this job instance.
* For debugging purposes only.
*/
public String toString() {
return getName() + "(" + jobNumber + ")"; //$NON-NLS-1$//$NON-NLS-2$
}
/* (non-Javadoc)
* @see Job#wakeUp(long)
*/
protected void wakeUp(long delay) {
manager.wakeUp(this, delay);
}
/**
* @param waitQueueStamp The waitQueueStamp to set.
* @GuardedBy("manager.lock")
*/
void setWaitQueueStamp(long waitQueueStamp) {
this.waitQueueStamp = waitQueueStamp;
}
/**
* @return Returns the waitQueueStamp.
* @GuardedBy("manager.lock")
*/
long getWaitQueueStamp() {
return waitQueueStamp;
}
}