/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed 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.
*
* Based on the 3rd version of SwingWorker (also known as SwingWorker 3), an
* abstract class that you subclass to perform GUI-related work in a dedicated
* thread. For instructions on using this class, see:
*
* http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html
*
* Note that the API changed slightly in the 3rd version:
* You must now invoke start() on the SwingWorker after
* creating it.
*/
package net.java.sip.communicator.plugin.desktoputil;
import java.util.concurrent.*;
import javax.swing.*;
import net.java.sip.communicator.util.*;
/**
* Utility class based on the javax.swing.SwingWorker. <tt>SwingWorker</tt> is
* an abstract class that you subclass to perform GUI-related work in a
* dedicated thread. In addition to the original SwingWorker this class takes
* care of exceptions occurring during the execution of the separate thread. It
* will call a catchException() method in the Swing thread if an exception
* occurs.
*
* @author Yana Stamcheva
* @author Lyubomir Marinov
*/
public abstract class SwingWorker
{
/** Logging instance for SwingWorker */
private final static Logger logger = Logger.getLogger(SwingWorker.class);
/**
* The <tt>ExecutorService</tt> which is shared by the <tt>SwingWorker</tt>
* instances for the purposes of controlling the use of <tt>Thread</tt>s.
*/
private static ExecutorService executorService;
/**
* The <tt>Callable</tt> implementation which is (to be) submitted to
* {@link #executorService} and invokes {@link #construct()} on behalf of
* this <tt>SwingWorker</tt>.
*/
private final Callable<Object> callable;
/**
* The <tt>Future</tt> instance which represents the state and the return
* value of the execution of {@link #callable} i.e. {@link #construct()}.
*/
private Future<?> future;
/**
* Start a thread that will call the <code>construct</code> method
* and then exit.
*/
public SwingWorker()
{
callable
= new Callable<Object>()
{
public Object call()
{
Object value = null;
try
{
value = construct();
}
catch (final Throwable t)
{
if (t instanceof ThreadDeath)
throw (ThreadDeath) t;
else
{
// catchException
SwingUtilities.invokeLater(
new Runnable()
{
public void run()
{
catchException(t);
}
});
}
}
// We only want to perform the finished if the thread hasn't
// been interrupted.
if (!Thread.currentThread().isInterrupted())
// finished
SwingUtilities.invokeLater(
new Runnable()
{
public void run()
{
finished();
}
});
return value;
}
};
}
/**
* Called on the event dispatching thread (not on the worker thread)
* if an exception has occurred during the <code>construct</code> method.
*
* @param exception the exception that has occurred
*/
protected void catchException(Throwable exception)
{
logger.error("unhandled exception caught", exception);
}
/**
* Computes the value to be returned by {@link #get()}.
*/
protected abstract Object construct()
throws Exception;
/**
* Called on the event dispatching thread (not on the worker thread)
* after the <code>construct</code> method has returned.
*/
protected void finished()
{
}
/**
* Return the value created by the <code>construct</code> method.
* Returns null if either the constructing thread or the current
* thread was interrupted before a value was produced.
*
* @return the value created by the <code>construct</code> method
*/
public Object get()
{
Future<?> future;
synchronized (this)
{
/*
* SwingWorker assigns a value to the future field only once and we
* do not want to invoke Future#cancel(true) while holding a lock.
*/
future = this.future;
}
Object value = null;
if (future != null)
{
boolean interrupted = false;
do
{
try
{
value = future.get();
break;
}
catch (CancellationException ce)
{
break;
}
catch (ExecutionException ee)
{
break;
}
catch (InterruptedException ie)
{
interrupted = true;
}
}
while (true);
if (interrupted) // propagate
Thread.currentThread().interrupt();
}
return value;
}
/**
* A new method that interrupts the worker thread. Call this method
* to force the worker to stop what it's doing.
*/
public void interrupt()
{
Future<?> future;
synchronized (this)
{
/*
* SwingWorker assigns a value to the future field only once and we
* do not want to invoke Future#cancel(true) while holding a lock.
*/
future = this.future;
}
if (future != null)
future.cancel(true);
}
/**
* Start the worker thread.
*/
public void start()
{
ExecutorService executorService;
synchronized (SwingWorker.class)
{
if (SwingWorker.executorService == null)
SwingWorker.executorService = Executors.newCachedThreadPool();
executorService = SwingWorker.executorService;
}
synchronized (this)
{
if (future == null || future.isDone())
future = executorService.submit(callable);
else
throw new IllegalStateException("future");
}
}
}