package uk.ac.uea.threadr;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import uk.ac.uea.threadr.internal.MemoryMonitor;
import uk.ac.uea.threadr.internal.logging.EventLogger;
import uk.ac.uea.threadr.internal.testing.SafetyTester;
import uk.ac.uea.threadr.internal.vmhandler.VMWrapper;
/**
* <p>Threadr public API. Contains all the methods required to use the Threadr
* API to execute tasks in a parallel manner based on their thread safety
* status.</p>
* <p>Threadr requires a collection of objects that implement the
* {@link ParallelTask} interface. An optional set of objects that implement
* the {@link ThreadTest} interface if specific, non-standard tests are needed.
* </p>
* <p>Results from this API are returned wrapped in a {@link ThreadrResult}
* object, which is a mapping of unique tests to their results.</p>
*
* @author Jordan Woerner
* @version 1.5
*/
public final class Threadr {
/**
* Logger for the Threadr API, outputs to the 'log' folder of the project
* using Threadr.
*
* @since 1.0
*/
public static final Logger logger = Logger.getLogger("uk.ac.uea.threadr");
/**
* Mapping of {@link ParallelTasks} and their {@link AbstractReturnType}
* results.
*/
private ThreadrResult tasks;
/** List of unique {@link ThreadTest} for thread safety testing. */
private Set<ThreadTest> tests;
/** Results from the thread safety testing. */
private Map<Class<?>, ThreadSafety> threadSafeties;
/** An array of parameters to be passed to the new VM instances. */
private String[] vmParameters;
/** A {@link SequentialHandler} to handle unsafe tasks. */
private SequentialHandler handler;
/** Monitors the memory used by Threadr. */
private MemoryMonitor monitor;
/**
* <p>Sets up Threadr with no tasks to work on and no external tests
* loaded.</p>
* <p>Threadr expects to be passed an array or List of classes that extend
* the {@link ParallelTask} interface. These tasks will be assessed for
* thread safety, and executed based on the result of this.</p>
* <p>Should the tasks need specialised testing for thread safety, Threadr
* can be passed an array or List of {@link ThreadTest} classes to use
* during testing.</p>
*
* @since 1.0
*/
public Threadr() {
this.tasks = new ThreadrResult();
this.tests = new HashSet<>();
this.threadSafeties = new HashMap<>();
this.handler = new IgnoreSequential();
this.monitor = MemoryMonitor.getInstance();
}
/**
* <p>Sets up Threadr with the specified array of tasks to work on.</p>
* <p>Threadr expects to be passed an array or List of classes that extend
* the {@link ParallelTask} interface. These tasks will be assessed for
* thread safety, and executed based on the result of this.</p>
* <p>Should the tasks need specialised testing for thread safety, Threadr
* can be passed an array or List of {@link ThreadTest} classes to use
* during testing.</p>
*
* @param tasks An array of objects that implement the ParallelTask
* interface.
* @since 1.0
*/
public Threadr(ParallelTask[] tasks) {
this.tasks = new ThreadrResult();
this.tests = new HashSet<>();
this.threadSafeties = new HashMap<>();
this.handler = new IgnoreSequential();
this.monitor = MemoryMonitor.getInstance();
addTasks(tasks); // Add the tasks to the task pool.
}
/**
* <p>Sets up Threadr with the specified List of tasks to work on. Tasks
* must be derived from the ParallelTask interface to be supported.</p>
* <p>Threadr expects to be passed an array or List of classes that extend
* the {@link ParallelTask} interface. These tasks will be assessed for
* thread safety, and executed based on the result of this.</p>
* <p>Should the tasks need specialised testing for thread safety, Threadr
* can be passed an array or List of {@link ThreadTest} classes to use
* during testing.</p>
*
* @param tasks A List of objects that implement the ParallelTask
* interface.
* @since 1.0
*/
public Threadr(List<? extends ParallelTask> tasks) {
this.tasks = new ThreadrResult();
this.tests = new HashSet<>();
this.threadSafeties = new HashMap<>();
this.handler = new IgnoreSequential();
this.monitor = MemoryMonitor.getInstance();
addTasks(tasks); // Add the tasks to the task pool.
}
/**
* <p>Sets up Threadr with the specified array of tasks and external tests.
* Tasks must be derived from the ParallelTask interface to be supported,
* external tests must extend the ThreadTest interface to be loaded.</p>
* <p>Threadr expects to be passed an array or List of classes that extend
* the {@link ParallelTask} interface. These tasks will be assessed for
* thread safety, and executed based on the result of this.</p>
* <p>Should the tasks need specialised testing for thread safety, Threadr
* can be passed an array or List of {@link ThreadTest} classes to use
* during testing.</p>
*
* @param tasks An array of objects that implement the ParallelTask
* interface.
* @param tests An array of objects that implement the ThreadTest
* interface.
* @since 1.5
*/
public Threadr(ParallelTask[] tasks, ThreadTest[] tests) {
this.tasks = new ThreadrResult();
this.tests = new HashSet<>();
this.threadSafeties = new HashMap<>();
this.handler = new IgnoreSequential();
this.monitor = MemoryMonitor.getInstance();
addTasks(tasks);
addTests(tests);
}
/**
* <p>Sets up Threadr with the specified list of tasks and external tests.
* Tasks must be derived from the ParallelTask interface to be supported,
* external tests must extend the ThreadTest interface to be loaded.</p>
* <p>Threadr expects to be passed an array or List of classes that extend
* the {@link ParallelTask} interface. These tasks will be assessed for
* thread safety, and executed based on the result of this.</p>
* <p>Should the tasks need specialised testing for thread safety, Threadr
* can be passed an array or List of {@link ThreadTest} classes to use
* during testing.</p>
*
* @param tasks A {@link List} of objects that implement the ParallelTask
* interface.
* @param tests A {@link List} of objects that implement the ThreadTest
* interface.
* @since 1.0
*/
public Threadr(List<? extends ParallelTask> tasks,
List<? extends ThreadTest> tests) {
this.tasks = new ThreadrResult();
this.tests = new HashSet<>();
this.threadSafeties = new HashMap<>();
this.handler = new IgnoreSequential();
this.monitor = MemoryMonitor.getInstance();
addTasks(tasks);
addTests(tests);
}
/**
* Sets the the external logging facility of Threadr. Calling this will
* cause Threadr to create a folder 'logs' in the working directory of the
* application.
*
* @since 1.3
*/
public final void enableLogging() {
try {
/* Set up external logging. */
EventLogger.setup();
} catch (IOException e) {
/* Thrown when EventLogger cannot set up the FileHandlers. */
System.err.println("Failed to set up logging!");
System.err.println(e.getMessage());
}
}
/**
* Adds the specified task to the Map of classes to be tested and run.
*
* @param task The {@link ParallelTask} to add to this instance of Threadr.
* @since 1.5
*/
public final void addTask(ParallelTask task) {
this.tasks.addTask(task);
}
/**
* Adds the specified task to the Map of classes to be tested and run. The
* thread safeness of a task may be specified using this method. If null is
* passed as the second parameter, the task will have its thread safety
* evaluated by the framework at runtime.
*
* @param task The {@link ParallelTask} to add to this instance of Threadr.
* @param safeness The thread safety of the task. Can be one of;
* {@link ThreadSafety#THREAD}, {@link ThreadSafety#VM}, or
* {@link ThreadSafety#SEQUENTIAL}.
* @return Returns the previous {@link ThreadSafety} value for the provided
* task if one exists, or null otherwise.
* @since 1.5
*/
public final ThreadSafety addTask(ParallelTask task, ThreadSafety safeness) {
this.tasks.addTask(task);
return threadSafeties.put(task.getClass(), safeness);
}
/**
* Adds the specified tasks to the Map of classes to be tested and run.
*
* @param tasks A {@link List} of objects that implement the ParallelTask
* interface.
* @since 1.0
*/
public final void addTasks(List<? extends ParallelTask> tasks) {
for (Object task : tasks) {
if (!this.tasks.addTask((ParallelTask)task)) {
logger.warning("Duplicate task provided!");
}
}
}
/**
* <p>Adds the specified array of tasks to the Map of classes to be tested
* and run.</p>
*
* @param tasks An array of objects that implement the ParallelTask
* interface.
* @since 1.0
*/
public final void addTasks(ParallelTask[] tasks) {
/* Use the List based addTasks method, less repeating code. */
addTasks(Arrays.asList(tasks));
}
/**
* Adds the tasks held in the provided {@link Map} to the Map of classes to
* be tested and run. Also stores the {@link ThreadSafety} associated with
* the class inside the provided map.
*
* ThreadSafety can be one of; {@link ThreadSafety#THREAD},
* {@link ThreadSafety#VM}, or {@link ThreadSafety#SEQUENTIAL}.
*
* @param tasks A mapping of the {@link ParallelTask} objects to add to
* this instance of Threadr to their thread safeness as a
* {@link ThreadSafety} enumeration. The mapped ThreadSafety can be null, which
* will force the framework to determine the thread safety at runtime.
* @return Returns a mapping of the existing {@link ThreadSafety} values for
* and classes that already existed in the test results.
* @since 1.5
*/
public final Map<? extends ParallelTask, ThreadSafety> addTasks(
Map<? extends ParallelTask, ThreadSafety> tasks) {
Map<ParallelTask, ThreadSafety> oldResults = new HashMap<>();
for (ParallelTask task : tasks.keySet()) {
this.tasks.addTask(task);
/* Get the desired ThreadSafety and the old result. */
ThreadSafety newResult = tasks.get(task);
ThreadSafety oldResult = threadSafeties.put(task.getClass(), newResult);
/* If the existing result was not null, we'll return that. */
if (oldResult != null) {
oldResults.put(task, oldResult);
}
}
return oldResults;
}
/**
* Adds a single test to be used during thread safety testing. Duplicate
* tests will not be added to the Set.
*
* @param test The test to add as an implementation of the
* {@link ThreadTest} interface.
* @since 1.5
*/
public final void addTest(ThreadTest test) {
if (!this.tests.add((ThreadTest)test)) {
logger.warning("Duplicate test provided!");
}
}
/**
* <p>Add a list of tests to the Set of tests to be used during thread
* safety testing. Duplicate tests will not be added to the Set.</p>
*
* @param tests A list of objects that implement the ThreadTest interface.
* @since 1.0
*/
public final void addTests(List<? extends ThreadTest> tests) {
for (Object test : tests) {
if (!this.tests.add((ThreadTest)test)) {
logger.warning("Duplicate test provided!");
}
}
}
/**
* <p>Add an array of tests to the Set of tests to be used during thread
* safety testing. Duplicate tests will not be added to the Set.</p>
*
* @param tests An array of objects that implement the ThreadTest
* interface.
* @since 1.0
*/
public final void addTests(ThreadTest[] tests) {
/* Use the existing addTests code to save time. */
addTests(Arrays.asList(tests));
}
/**
* <p>Allows custom parameters to be passed to new instances of the Java
* Virtual Machine.</p>
* <p>Invalid parameters may cause the VM to fail to launch,
* this won't be detected until execution and will cause issues with any
* tasks assigned to new Virtual Machines.</p>
* <p>Available JVM options can be found
* <a href="http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html">
* here</a></p>
*
* @param args And array of Strings, with each element being a command line
* option or parameter value for an option.
* @since 1.1
*/
public final void setVMParameters(String...args) {
this.vmParameters = args;
}
/**
* Sets the {@link SequentialHandler} this instance of Threadr should use
* to handle {@link ParallelTask}s that are not thread or virtual machine
* safe.
*
* @param newHandler The new SequentialHandler to use.
* @since 1.2
*/
public final void setSequentialHandler(SequentialHandler newHandler) {
logger.info(String.format("Using SequentialHandler '%s'.",
newHandler.getClass().getName()));
this.handler = newHandler;
}
/**
* Gets the tasks to be executed by this {@link Threadr} instance.
*
* @return The tasks as a {@link Set} of {@link ParallelTask} instances.
* @since 1.4
*/
public final Set<? extends ParallelTask> getTasks() {
return tasks.getTasks();
}
/**
* Gets the thread safeness tests to be used this {@link Threadr} instance.
*
* @return The tasks as a {@link Set} of {@link ThreadTest} instances.
* @since 1.4
*/
public final Set<? extends ThreadTest> getTests() {
return tests;
}
/**
* Gets the stored thread safeness testing results from this instance of
* {@link Threadr}. The classes for each task are mapped to their result as
* a {@link ThreadSafety} value.
*
* @return The mapping of {@link Class} objects to their
* {@link ThreadSafety} values for thread safeness.
* @since 1.5
*/
public final Map<Class<?>, ThreadSafety> getThreadSafetyResults() {
return threadSafeties;
}
/**
* Gets the command line parameters to be used when creating new Java
* Virtual Machine instances.
*
* @return The parameters as an array of Strings.
* @since 1.4
*/
public final String[] getVMParameters() {
return vmParameters;
}
/**
* Tests the thread safety of the tasks currently held in this instance of
* {@link Threadr}. This method is called during the
* {@link Threadr#execute()} method to ensure that the thread safety values
* are up to date and exist for all tasks. To see the thread safety test
* results, call the {@link Threadr#getThreadSafetyResults()} method.
*
* @since 1.5
*/
public final void testSafety() {
SafetyTester tester = new SafetyTester();
tester.provideTests(tests); // Add our custom tests.
/* Test all the unique classes passed to the framework. */
for (Object t : tasks.getTasks()) {
if (!threadSafeties.containsKey(t.getClass())
|| threadSafeties.get(t.getClass()) == null) {
/* Test the thread safeness of tasks that haven't been tested
or tasks that have a null result. */
ThreadSafety threadSafety = tester.testSafety(t);
threadSafeties.put(t.getClass(), threadSafety);
}
}
}
/**
* <p>Tests all the tasks provided to the API using the built in tests and
* any provided tests.</p>
* <p>Classes are tested for their thread safety, and based on the results
* of the testing, are run in their own thread, in a new instance of the
* Java Virtual Machine, or sequentially.</p>
*
* @return A mapping of the tasks to their AbstractReturnType results as a
* {@link ThreadrResult} instance.
* @since 1.0
*/
public final ThreadrResult execute() {
if (tasks.size() == 0) {
/* Don't bother running anything if there are no tasks. */
logger.info("No tasks provided.");
return null; // If things go wrong, just return null.
}
/* Start the memory monitoring thread. */
Thread monitorThread = new Thread(monitor);
monitorThread.start();
/* Set up the ExecutorService that will handle threading. */
ExecutorService executor = Executors.newCachedThreadPool();
testSafety(); // Run the thread safety tests.
Map<ParallelTask, Future<AbstractReturnType<?>>> futures =
new HashMap<>();
/* Execute each task based on whether they are thread or VM safe. */
for (Object t : tasks.getTasks()) {
ParallelTask task = (ParallelTask)t;
switch (threadSafeties.get(t.getClass())) {
case SEQUENTIAL: // If the task is not safe.
AbstractReturnType<?> result = handler.handle(task);
if (result != null) {
/* If the handler returned a result, store it. */
tasks.addResult(task, result);
}
break;
case THREAD: // The task is thread safe.
futures.put(task, runThread(executor, task));
break;
case VM: // The task is virtual-machine safe.
futures.put(task, runVM(executor, task));
break;
default:
logger.warning("Unknown test result found during execution.");
}
}
/* Attempt to grab the returned AbstractReturnTypes. */
for (Object o : futures.keySet()) {
ParallelTask task = (ParallelTask)o;
Future<AbstractReturnType<?>> result = futures.get(task);
try {
if (result != null) {
tasks.addResult(task, result.get());
} else {
/* Remove tasks that fail to return. */
logger.warning(String.format(
"Task %s failed to return a result.",
task.getClass().getName()));
tasks.removeResult(task);
}
} catch (ExecutionException exEx) {
/* If something goes wrong during execution, log the error. */
logger.severe(String.format("Execution error in class %s."
+ "\nMessage: %s",
task.getClass().getName(), exEx.getMessage()));
} catch (InterruptedException inEx) {
logger.severe(String.format("Execution of class %s "
+ "interrupted.\nReason: %s",
task.getClass().getName(), inEx.getMessage()));
}
}
executor.shutdown(); // Done with the ExecutorService now.
/* Perform any post-execution operations in the selected handler. */
ThreadrResult sequentialResults = handler.postExecution();
if (sequentialResults != null) {
tasks.combine(sequentialResults);
}
/* Stop the memory monitor. */
try {
monitorThread.join();
} catch (InterruptedException iEx) {
logger.warning("Could not join memory monitor in main thread.");
}
return tasks;
}
/**
* <p>Runs the provided ParallelTask in a new thread. Results of execution
* are passed back contained in an AbstractReturnType.</p>
*
* @param executor The ExecutorService to run the provided ParallelTask in
* a new thread.
* @param task The ParallelTask to be executed in a new thread. Task is
* final to prevent modification of the reference by the thread.
* @return The results of execution as an AbstractReturnType contained in a
* Future instance.
* @since 1.0
*/
private final Future<AbstractReturnType<?>> runThread(
final ExecutorService executor,
final ParallelTask task) {
/* Create a new Future holding an AbstractReturnType. */
Future<AbstractReturnType<?>> result = executor.submit(task);
return result; // Return the Future.
}
/**
* <p>Runs the provided ParallelTask in a new Java Virtual machine
* instance. Results of execution are passed back contained in an
* AbstractReturnType.</p>
*
* @param executor The ExecutorService to handle the thread that will track
* external JVM progress.
* @param task The ParallelTask to execute in a new JVM instance.
* @return The results of execution as an AbstractReturnType contained in a
* Future instance.
* @since 1.0
*/
private final Future<AbstractReturnType<?>> runVM(
final ExecutorService executor,
final ParallelTask task) {
Future<AbstractReturnType<?>> result = null;
/* Stick the task inside a VMWrapper and send it to be executed. */
VMWrapper newVM = null;
if (vmParameters != null
&& vmParameters.length > 0) {
/* If there are custom parameters, pass them to the VM instance. */
newVM = new VMWrapper(task, vmParameters);
} else {
/* Otherwise, just create the instance. */
newVM = new VMWrapper(task);
}
/* Try and execute the provided task in a new VM. */
result = executor.submit(newVM);
return result;
}
/**
* Gets the {@link MemoryMonitor} attached to this instance of Threadr.
*
* @return The MemoryMonitor in use.
*/
public final MemoryMonitor getMemoryMonitor() {
return monitor;
}
/**
* <p>Resets Threadr to its starting state. All tasks, results, tests and
* virtual machine parameters are removed.</p>
*
* @since 1.1
*/
public final void reset() {
tasks.clear();
tests.clear();
vmParameters = null;
monitor.clear();
}
/**
* <p>Set the detail of the logging output of Threadr.</p>
*
* @param output The output detail as a logging API Level.
* @since 1.0
*/
public final void setOutputLevel(Level output) {
EventLogger.setLevel(output);
}
/**
* Gets details about this instance of Threadr. Details include the number
* of tasks, the number of tests and the Virtual Machine parameters in use.
*
* @return The details of this instance as a String.
* @since 1.3
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Threadr - Tasks: ").append(tasks.size());
sb.append(", Tests: ").append(tests.size());
sb.append(", VM Parameters:\n\t");
for (String param : vmParameters) {
sb.append(param).append(", ");
}
return sb.toString();
}
}