org.openide.util.Task Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide.util;
import java.util.HashSet;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
/** A task that may be executed in a separate thread and permits examination of its status.
* Other threads can check if it is finished or wait for it
* to finish.
*
* For example:
*
* Runnable r = new Runnable () {
* public void run () {
* // do something
* }
* };
* Task task = new Task (r);
* RequestProcessor.postRequest (task);
*
*
In a different thread one can then test task.isFinished ()
* or wait for it with task.waitFinished ()
.
*
* @author Jaroslav Tulach
*/
public class Task extends Object implements Runnable {
/** Dummy task which is already finished. */
public static final Task EMPTY = new Task();
private static final Logger LOG = Logger.getLogger(Task.class.getName());
static {
EMPTY.finished = true;
}
/** map of subclasses to booleans whether they override waitFinished() or not
*/
private static java.util.WeakHashMap overrides;
/** request processor for workarounding compatibility problem with
* classes that do not override waitFinished (long)
*/
private static RequestProcessor RP;
/** what to run */
final Runnable run;
/** flag if we have finished */
private boolean finished;
/** listeners for the finish of task (TaskListener) */
private HashSet list;
/** Create a new task.
* The runnable should provide its own error-handling, as
* by default thrown exceptions are simply logged and not rethrown.
* @param run runnable to run that computes the task
*/
public Task(Runnable run) {
this.run = run;
if (run == null) {
finished = true;
}
}
/** Constructor for subclasses that wants to control whole execution
* itself.
* @since 1.5
*/
protected Task() {
this.run = null;
}
/** Test whether the task has finished running.
* @return true
if so
*/
public final boolean isFinished() {
synchronized (this) {
return finished;
}
}
/** Wait until the task is finished.
* Changed not to be final
in version 1.5
*/
public void waitFinished() {
synchronized (this) {
while (!finished) {
try {
wait();
} catch (InterruptedException ex) {
}
}
}
}
/** Wait until the task is finished, but only a given time.
* waitFinished(0) means indefinite timeout (similar to wait(0))
* @param milliseconds time in milliseconds to wait for the result
* @exception InterruptedException when the waiting has been interrupted
* @return true if the task is really finished, or false if the time out
* has been exceeded
* @since 5.0
*/
public boolean waitFinished(long milliseconds) throws InterruptedException {
synchronized (this) {
if (overridesTimeoutedWaitFinished()) {
// the the task overrides waitFinished (timeout) or is
// one of the basic tasks, then we can just simply do our bese
// code. Otherwise we have to execute threading workaround
if (finished) {
return true;
}
long expectedEnd = System.currentTimeMillis() + milliseconds;
for (;;) {
LOG.log(Level.FINE, "About to wait {0} ms", milliseconds);
wait(milliseconds);
if (finished) {
LOG.log(Level.FINER, "finished, return"); // NOI18N
return true;
}
if (milliseconds == 0) {
LOG.log(Level.FINER, "infinite wait, again"); // NOI18N
continue;
}
long now = System.currentTimeMillis();
long remains = expectedEnd - now;
LOG.log(Level.FINER, "remains {0} ms", remains);
if (remains <= 0) {
LOG.log(Level.FINER, "exit, timetout");
return false;
}
milliseconds = remains;
}
}
}
// as we know that RequestProcessor implements the waitFinished(long)
// correctly we just post a task for waitFinished() into some
// of its threads and wait just the given milliseconds time
// for the result, by that we can guarantee the semantics
// of the call
class Run implements Runnable {
@Override
public void run() {
Task.this.waitFinished();
}
}
LOG.fine("Using compatibility waiting");
RequestProcessor.Task task = RP.post(new Run());
return task.waitFinished(milliseconds);
}
/** Changes the state of the task to be running. Any call after this
* one and before notifyFinished to waitFinished blocks.
* @since 1.5
*/
protected final void notifyRunning() {
synchronized (this) {
RequestProcessor.logger().log(Level.FINE, "notifyRunning: {0}", this); // NOI18N
this.finished = false;
notifyAll();
}
}
/** Notify all waiters that this task has finished.
* @see #run
*/
protected final void notifyFinished() {
Iterator it;
synchronized (this) {
finished = true;
RequestProcessor.logger().log(Level.FINE, "notifyFinished: {0}", this); // NOI18N
notifyAll();
// fire the listeners
if (list == null) {
return;
}
it = ((HashSet) list.clone()).iterator();
}
while (it.hasNext()) {
TaskListener l = it.next();
l.taskFinished(this);
}
}
/** Start the task.
* When it finishes (even with an exception) it calls
* {@link #notifyFinished}.
* Subclasses may override this method, but they
* then need to call {@link #notifyFinished} explicitly.
* Note that this call runs synchronously, but typically the creator
* of the task will call this method in a separate thread.
*/
public void run() {
try {
notifyRunning();
if (run != null) {
run.run();
}
} finally {
notifyFinished();
}
}
/** Add a listener to the task. The listener will be called once the
* task {@link #isFinished()}. In case the task is already finished, the
* listener is called immediately.
*
* @param l the listener to add
*/
public void addTaskListener(TaskListener l) {
boolean callNow;
synchronized (this) {
if (list == null) {
list = new HashSet();
}
list.add(l);
callNow = finished;
}
if (callNow) {
l.taskFinished(this);
}
}
/** Remove a listener from the task.
* @param l the listener to remove
*/
public synchronized void removeTaskListener(TaskListener l) {
if (list == null) {
return;
}
list.remove(l);
}
public String toString() {
return "task " + run; // NOI18N
}
/** Checks whether the class overrides wait finished.
*/
private boolean overridesTimeoutedWaitFinished() {
// yes we implement it corretly
if (getClass() == Task.class) {
return true;
}
// RequestProcessor.Task overrides correctly
if (getClass() == RequestProcessor.Task.class) {
return true;
}
java.util.WeakHashMap m;
Boolean does;
synchronized (Task.class) {
if (overrides == null) {
overrides = new java.util.WeakHashMap();
RP = new RequestProcessor("Timeout waitFinished compatibility processor", 255); // NOI18N
}
m = overrides;
does = m.get(getClass());
if (does != null) {
return does.booleanValue();
}
try {
java.lang.reflect.Method method = getClass().getMethod("waitFinished", new Class[] { Long.TYPE }); // NOI18N
does = Boolean.valueOf(method.getDeclaringClass() != Task.class);
m.put(getClass(), does);
return does.booleanValue();
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
return true;
}
}
}
/** Reveal the identity of the worker runnable.
* Used for debugging from RequestProcessor.
*/
String debug() {
return (run == null) ? "null" : run.getClass().getName();
}
}