package org.codefx.tarkastus;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.embed.swing.JFXPanel;
import javax.swing.SwingUtilities;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
* A rule which evaluates all statements (i.e. runs all tests) on the JavaFX platform thread.
* <p>
* The platform thread is created on demand.
* <p>
* TODO: superficial tests
*/
public class JavaFXRule implements TestRule {
@Override
public Statement apply(Statement statement, Description description) {
return new JavaFXStatement(statement);
}
/**
* Evaluates statements on the JavaFX platform thread.
*/
private static class JavaFXStatement extends Statement {
private final StatementOnPlatformThread statement;
/**
* Creates a new JavaFX statement.
*
* @param statement
* the statement which will be evaluated on the platform thread
*/
public JavaFXStatement(Statement statement) {
this.statement = new StatementOnPlatformThread(statement);
}
@Override
public void evaluate() throws Throwable {
JavaFXInitializer.ensureInitialized();
statement.evaluate();
}
}
/**
* Ensures that JavaFX is initialized.
*/
private static class JavaFXInitializer {
private static boolean initialized = false;
/**
* Ensures that JavaFX is initialized.
*
* @throws InterruptedException
* when waiting for the initialization (which happens in another thread) to complete is interrupted
*/
public static void ensureInitialized() throws InterruptedException {
if (initialized)
return;
initializeOnce();
}
private static synchronized void initializeOnce() throws InterruptedException {
if (initialized)
return;
initialize();
initialized = true;
}
@SuppressWarnings("unused")
private static void initialize() throws InterruptedException {
/*
* To initialize JavaFX, create a JFXPanel on the Swing EDT.
*/
final CountDownLatch initialized = new CountDownLatch(1);
SwingUtilities.invokeLater(() -> {
new JFXPanel();
initialized.countDown();
});
initialized.await();
}
}
/**
* Evaluates a statement on the JavaFX platform thread.
*/
private static class StatementOnPlatformThread {
private final Statement statement;
/**
* Creates a new statement.
*
* @param statement
* the statement which will be evaluated on the platform thread
*/
public StatementOnPlatformThread(Statement statement) {
this.statement = statement;
}
/**
* Evaluates the statement specified during construction
*
* @throws Throwable
* when the statement evaluation throws an exception
*/
public void evaluate() throws Throwable {
Property<Optional<Throwable>> caughtThrowable = new SimpleObjectProperty<>(Optional.empty());
CountDownLatch evaluated = new CountDownLatch(1);
Platform.runLater(() -> {
caughtThrowable.setValue(evaluateOnPlatform(statement));
evaluated.countDown();
});
evaluated.await();
// make sure to rethrow any exception which occurred during evaluation
if (caughtThrowable.getValue().isPresent())
throw caughtThrowable.getValue().get();
}
private static Optional<Throwable> evaluateOnPlatform(Statement statement) {
try {
statement.evaluate();
return Optional.empty();
} catch (Throwable ex) {
return Optional.of(ex);
}
}
}
}