package bluej.runtime;
import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.AWTEventListener;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.application.Preloader;
import javafx.collections.ListChangeListener.Change;
import javafx.embed.swing.JFXPanel;
import javafx.stage.Stage;
import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
import org.junit.runner.Request;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import bluej.utility.Utility;
import org.junit.runner.notification.RunListener;
import threadchecker.OnThread;
import threadchecker.Tag;
import javax.swing.SwingUtilities;
| Class that controls the runtime of code executed within BlueJ.
| Sets up the initial thread state, etc.
|
| <p>This class both holds runtime attributes and executes commands.
| Execution is done through JDI reflection from the JdiDebugger class.
|
| @author Michael Kolling
| @author Andrew Patterson
|
public class ExecServer
{
public static final String MAIN_THREAD_NAME = "mainThread";
public static Thread mainThread = null;
public static final String WORKER_THREAD_NAME = "workerThread";
public static Thread workerThread = null;
public static final int RUN_ON_DEFAULT_THREAD = 0;
public static final int RUN_ON_FX_THREAD = 1;
public static final int RUN_ON_SWING_THREAD = 2;
public static final int RUN_ON_CUSTOM_THREAD = 3;
public static int threadToRunOn = RUN_ON_DEFAULT_THREAD;
private static Consumer<Runnable> customThreadRunner;
public static String classToRun;
public static String methodToRun;
public static String [] parameterTypes;
public static Object [] arguments;
public static int execAction = -1;
public static Object methodReturn;
public static Class<?> executedClass;
public static Throwable exception;
public static final String RUN_ON_THREAD_NAME = "threadToRunOn";
public static final String CLASS_TO_RUN_NAME = "classToRun";
public static final String METHOD_TO_RUN_NAME = "methodToRun";
public static final String PARAMETER_TYPES_NAME = "parameterTypes";
public static final String ARGUMENTS_NAME = "arguments";
public static final String EXEC_ACTION_NAME = "execAction";
public static final String METHOD_RETURN_NAME = "methodReturn";
public static final String EXCEPTION_NAME = "exception";
public static final String EXECUTED_CLASS_NAME = "executedClass";
public static final int EXEC_SHELL = 0;
public static final int TEST_SETUP = 1;
public static final int TEST_RUN = 2;
public static final int DISPOSE_WINDOWS = 3;
public static final int EXIT_VM = 4;
public static final int LOAD_INIT_CLASS = 5;
public static final int INSTANTIATE_CLASS = 6;
public static final int INSTANTIATE_CLASS_ARGS = 7;
public static final int LAUNCH_FX_APP = 8;
public static int workerAction = EXIT_VM;
public static String objectName;
public static Object object;
public static String classPath;
public static String className;
public static String scopeId;
public static ClassLoader classLoader = null;
public static Object workerReturn;
public static final String WORKER_ACTION_NAME = "workerAction";
public static final String OBJECTNAME_NAME = "objectName";
public static final String OBJECT_NAME = "object";
public static final String CLASSPATH_NAME = "classPath";
public static final String CLASSNAME_NAME = "className";
public static final String WORKER_RETURN_NAME = "workerReturn";
public static final String SCOPE_ID_NAME = "scopeId";
public static final String CLASSLOADER_NAME = "classLoader";
public static final int REMOVE_OBJECT = 0;
public static final int ADD_OBJECT = 1;
public static final int LOAD_CLASS = 2;
public static final int NEW_LOADER = 3;
public static final int LOAD_ALL = 5;
private static ClassLoader currentLoader;
private static Map<String,BJMap<String,Object>> objectMaps = new HashMap<String,BJMap<String,Object>>();
| We need to keep track of open windows so that we can dispose of them
| when simulating a System.exit() call
|
private static List<Window> openWindows = Collections.synchronizedList(new LinkedList<Window>());
private static boolean disposingAllWindows = false;
| Main method.
|
public static void main(String[] args)
throws Throwable
{
System.setIn(new BJInputStream(System.in));
if (args.length > 0 && !args[0].equals("")) {
try {
System.setOut(new PrintStream(System.out, true, args[0]));
System.setErr(new PrintStream(System.err, true, args[0]));
}
catch (UnsupportedEncodingException uee) {
}
}
workerThread = new Thread("BlueJ worker thread")
{
public void run()
{
while (true) {
vmSuspend();
switch(workerAction) {
case ADD_OBJECT:
addObject(scopeId, objectName, object);
object = null;
break;
case REMOVE_OBJECT:
removeObject(scopeId, objectName);
break;
case LOAD_CLASS:
try {
if (classLoader == null)
classLoader = currentLoader;
workerReturn = Class.forName(className, false, currentLoader);
((Class<?>) workerReturn).getFields();
classLoader = null;
}
catch(Throwable cnfe) {
workerReturn = null;
}
break;
case NEW_LOADER:
workerReturn = newLoader(classPath);
break;
case EXIT_VM:
System.exit(0);
case LOAD_ALL:
workerReturn = loadAllClasses(className);
}
workerAction = EXIT_VM;
}
}
};
Toolkit toolkit = Toolkit.getDefaultToolkit();
AWTEventListener listener = new AWTEventListener()
{
public void eventDispatched(AWTEvent event)
{
Object source = event.getSource();
if (event.getID() == WindowEvent.WINDOW_OPENED) {
if (source instanceof Window) {
addWindow((Window) source);
Utility.bringToFront((Window) source);
}
}
else if (event.getID() == WindowEvent.WINDOW_CLOSED) {
if (source instanceof Window) {
removeWindow((Window) source);
}
}
}
};
toolkit.addAWTEventListener(listener, AWTEvent.WINDOW_EVENT_MASK);
newThread();
workerThread.setPriority(Thread.MAX_PRIORITY);
workerThread.start();
}
| This method is used to suspend the execution of the
| machine to indicate that everything is up and running.
|
public static void vmStarted()
{
}
| This method is used to suspend the execution of the worker threads.
| This is done via a breakpoint: a breakpoint is set in this method
| so calling this method suspends execution.
|
public static void vmSuspend()
{
}
| This method is used to show the terminal window in case
| the java program asks for input from the user.
|
public static void showTerminalOnInput()
{
}
| Add the object to our list of open windows
|
| @param o a window object which has just been opened
|
private static void addWindow(Window o)
{
openWindows.add(o);
}
| Remove the object from our list of open windows
|
| @param o a window object which has just been closed
|
private static void removeWindow(Window o)
{
if (!disposingAllWindows)
openWindows.remove(o);
}
| Find a scoping Map for the given scopeId
|
p.public static BJMap getScope(String scopeId)
{
synchronized (objectMaps) {
BJMap<String,Object> m = objectMaps.get(scopeId);
if (m == null) {
m = new BJMap<String,Object>();
objectMaps.put(scopeId, m);
}
return m;
}
}
| Create a new class loader for a given classpath.
| @param urlListAsString a URL list written as a single string (the \n is used to divide entries)
| @return a URLClassLoader that can be used to load user classes.
|
private static ClassLoader newLoader(String urlListAsString )
{
String [] splits = urlListAsString.split("\n");
URL []urls = new URL[splits.length];
for (int index = 0; index < splits.length; index++)
try {
urls[index] = new URL(splits[index]);
}
catch (MalformedURLException mfue) {
System.err.println("ExecServer.newLoader() Malformed URL=" + splits[index]);
}
currentLoader = new URLClassLoader(urls);
synchronized (objectMaps) {
objectMaps.clear();
}
return currentLoader;
}
| Load (and prepare) a class in the remote runtime. Return null if the class could not
| be loaded.
|
public static Class> loadAndInitClass(String className)
{
Throwable exception = null;
Class<?> cl;
try {
cl = Class.forName(className, true, currentLoader);
}
catch (ClassNotFoundException cnfe) {
cl = null;
}
catch (ExceptionInInitializerError eiie) {
exception = eiie.getCause();
try {
cl = Class.forName(className, false, currentLoader);
}
catch (ClassNotFoundException cnfe) {
cl = null;
}
}
catch (Throwable err) {
exception = err;
try {
cl = Class.forName(className, false, currentLoader);
}
catch (Throwable t) {
cl = null;
}
}
if (exception != null) {
StackTraceElement [] stackTrace = exception.getStackTrace();
int i;
for (i = stackTrace.length - 1; i > 0; i--) {
String stClassName = stackTrace[i].getClassName();
if (! stClassName.startsWith("bluej.runtime.ExecServer")
&& ! stClassName.startsWith("java.lang.Class"))
break;
}
StackTraceElement [] newStackTrace = new StackTraceElement[i+1];
System.arraycopy(stackTrace, 0, newStackTrace, 0, i+1);
exception.setStackTrace(newStackTrace);
recordException(exception);
}
return cl;
}
| Load a class, and all its inner classes.
|
private static Class>[] loadAllClasses(String className)
{
List<Class<?>> l = new ArrayList<Class<?>>();
try {
Class<?> c = currentLoader.loadClass(className);
c.getFields();
l.add(c);
getDeclaredInnerClasses(c, l);
int i = 1;
while (true) {
c = currentLoader.loadClass(className + '$' + i);
c.getFields();
l.add(c);
i++;
}
}
catch (Throwable t) {
}
return (Class []) l.toArray(new Class[l.size()]);
}
| Add the declared inner classes of the given class to the given
| list, recursively.
|
private static void getDeclaredInnerClasses(Class<?> c, List<Class<?>> list)
{
try {
Class<?> [] rlist = c.getDeclaredClasses();
for (int i = 0; i < rlist.length; i++) {
c = rlist[i];
c.getFields();
list.add(rlist[i]);
getDeclaredInnerClasses(rlist[i], list);
}
}
catch (Throwable t) {
}
}
| Add an object into a package scope (for possible use as parameter
| later). Used after object creation to add the newly created object
| to the scope.
|
| Must be static because it is used by Shell without a execServer reference
|
p.public static void addObject(String scopeId, String instanceName, Object value)
{
BJMap<String,Object> scope = getScope(scopeId);
synchronized (scope) {
scope.put(instanceName, value);
scope.notify();
}
}
| Execute a JUnit test case setUp method.
|
| @return an array consisting of String, Object pairs. For n fixture objects
| there will be n*2 entries in the array. Putting it in an array saves
| having to make lots of reflective List and HashMap calls on the
| calling virtual machine. Once the calling VM gets this array it can
| put it into a more suitable data structure itself.
|
private static Object[] runTestSetUp(String className)
{
Class<?> cl = loadAndInitClass(className);
try {
Object testCase = null;
Class<?> [] partypes = new Class[1];
partypes[0] = String.class;
try {
Constructor<?> ct = cl.getConstructor(partypes);
Object arglist[] = new Object[1];
arglist[0] = "TestCase " + className;
testCase = ct.newInstance(arglist);
}
catch(NoSuchMethodException nsme) {
testCase = null;
}
if (testCase == null) {
testCase = cl.getDeclaredConstructor().newInstance();
}
Method setUpMethod = findMethod(cl, "setUp", null);
if (setUpMethod != null) {
setUpMethod.setAccessible(true);
setUpMethod.invoke(testCase, (Object []) null);
}
Field fields[] = cl.getDeclaredFields();
Object obs[] = new Object[fields.length*2 + 1];
for (int i=0; i<fields.length; i++) {
fields[i].setAccessible(true);
obs[i*2] = fields[i].getName();
obs[i*2+1] = fields[i].get(testCase);
}
obs[obs.length-1] = testCase;
return obs;
}
catch (Throwable e) {
e.printStackTrace();
}
return new Object[0];
}
| Find a method in the class, regardless of visibility. This is
| essentially the same as Class.getMethod(), except that it also returns
| non-public methods.
|
| @param cl The class to search
| @param name The name of the method
| @param paramtypes The argument types
| @return The method, or null if not found.
|
static private Method findMethod(Class<?> cl, String name, Class<?>[] paramtypes)
{
while (cl != null){
try {
return cl.getDeclaredMethod(name, paramtypes);
}
catch (NoSuchMethodException nsme) {
}
cl = cl.getSuperclass();
}
return null;
}
| A class to record successes and failures during a JUnit test run.
|
private static class TestRecorder
extends RunListener
{
private final List<Object[]> testDetails = new ArrayList<>();
@Override
public void testFinished(Description description) throws Exception
{
if (!testDetails.isEmpty() && testDetails.get(testDetails.size() - 1)[0].equals(description.getMethodName()))
{
return;
}
Object[] r = new Object[8];
r[0] = description.getMethodName();
r[1] = r[2] = r[3] = r[4] = r[5] = r[6] = "";
r[7] = "success";
testDetails.add(r);
}
@Override
public void testFailure(Failure failure) throws Exception
{
Object[] r = new Object[8];
r[0] = failure.getDescription().getMethodName();
if (java.lang.AssertionError.class.isAssignableFrom(failure.getException().getClass())
|| failure.getException().getClass() == junit.framework.AssertionFailedError.class)
{
r[7] = "failure";
}
else
{
r[7] = "error";
}
r[1] = failure.getMessage() != null ? failure.getMessage() : "no exception message";
r[2] = failure.getTrace() != null ? failure.getTrace() : "no trace";
StackTraceElement [] ste = failure.getException().getStackTrace();
int k = 0;
while (k < ste.length && ste[k].getClassName().startsWith("org.junit."))
{
k++;
}
r[3] = ste[k].getClassName();
r[4] = ste[k].getFileName();
r[5] = ste[k].getMethodName();
r[6] = String.valueOf(ste[k].getLineNumber());
testDetails.add(r);
}
}
| Execute a JUnit test on a single test method or all test methods in a test class
| and return the result.<p>
|
| The array returned in case of failure/error has a length of [1 + 8*(number of methods tested)].
| The first item of the array contains the runtime of executing all tests in milliseconds expressed
| as a decimal integer, then each test has eight consecutive items in the array which
| contains:
| [0] = the method name
| [1] = the exception message (or "no exception message"), blank if success<br>
|* [2] = the stack trace as a string (or "no stack trace"), blank if success<br>
* [3] = the name of the class in which the exception/failure occurred, blank if success<br>
* [4] = the source filename for where the exception/failure occurred, blank if success
| [5] = the name of the method in which the exception/failure occurred, blank if success
| [6] = the line number where the exception/failure occurred (a string), blank if success
| [7] = "failure" or "error" or "success" (string)<br>
|*
* @return an array of length [1 + 8*(number of tests run)]
*/
private static Object[] runTestMethod(String className, String methodName)
{
Class<?> cl = loadAndInitClass(className);
|
|Result res;
|
|JUnitCore jUnitCore = new JUnitCore();
|
|TestRecorder recorder = new TestRecorder();
|
|jUnitCore.addListener(recorder);
|
|if (methodName != null)
|
|{
|
|res = jUnitCore.run(Request.method(cl, methodName));
|
|}
|
|else
|
|{
|
|res = jUnitCore.run(Request.aClass(cl));
|
|}
|
|return Stream.concat(Stream.of(String.valueOf(res.getRunTime())),
|
|recorder.testDetails.stream().flatMap(t -> Arrays.stream(t))).toArray();
|
|}
|
|/**
| Remove an object from the scope.
|
private static void removeObject(String scopeId, String instanceName)
{
BJMap<String,Object> scope = getScope(scopeId);
synchronized (scope) {
scope.remove(instanceName);
}
}
| Dispose of all the top level windows we think are open.
|
| Must be static because it is used by RemoteSecurityManager without a execServer reference
|
private static void disposeWindows()
{
synchronized(openWindows) {
disposingAllWindows = true;
Iterator<Window> it = openWindows.iterator();
while (it.hasNext()) {
it.next().dispose();
}
openWindows.clear();
disposingAllWindows = false;
}
}
| Clear the system input buffer. This is used between method calls to
| make sure that System.in.read() doesn't read input which was buffered
| during the last method call but never read.
|
private static void clearInputBuffer()
{
try {
int n = System.in.available();
while (n != 0) {
System.in.skip(n);
n = System.in.available();
}
}
catch(IOException ioe) {
}
}
private static interface RunnableThrows
{
public void run() throws Throwable;
}
| Bug in the java debug VM means that exception events are unreliable
| if we re-use the same thread over and over. So, whenever running user
| code results in an exception, this method is used to spawn a new thread.
|
private static void newThread()
{
final Thread oldThread = mainThread;
mainThread = new Thread("main") {
public void run()
{
try {
if (oldThread != null) {
oldThread.join();
}
}
catch(InterruptedException ie) {
}
vmStarted();
methodReturn = null;
exception = null;
Thread.currentThread().setContextClassLoader(currentLoader);
try {
switch(execAction) {
case EXEC_SHELL:
{
methodReturn = null;
executedClass = null;
clearInputBuffer();
Class<?> c = currentLoader.loadClass(classToRun);
executedClass = c;
Method m = c.getMethod("run", new Class[0]);
runOnTargetThread(() -> {
try
{
methodReturn = m.invoke(null, new Object[0]);
}
catch (InvocationTargetException ite)
{
throw ite.getCause();
}
});
break;
}
case INSTANTIATE_CLASS:
{
clearInputBuffer();
Class<?> c = currentLoader.loadClass(classToRun);
Constructor<?> cons = c.getDeclaredConstructor(new Class[0]);
cons.setAccessible(true);
runOnTargetThread(() -> {
try {
methodReturn = cons.newInstance((Object []) null);
}
catch (InvocationTargetException ite) {
throw ite.getCause();
}
});
break;
}
case INSTANTIATE_CLASS_ARGS:
{
clearInputBuffer();
Class<?> c = currentLoader.loadClass(classToRun);
Class<?> [] paramClasses = new Class[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
if (classLoader == null)
classLoader = currentLoader;
paramClasses[i] = Class.forName(parameterTypes[i], false, currentLoader);
}
Constructor<?> cons = c.getDeclaredConstructor(paramClasses);
cons.setAccessible(true);
runOnTargetThread(() -> {
try {
methodReturn = cons.newInstance(arguments);
}
catch (InvocationTargetException ite) {
throw ite.getCause();
}
});
break;
}
case LAUNCH_FX_APP:
CompletableFuture<Application> theApp = new CompletableFuture<>();
new Thread(() -> {
FXPreloader.theApp = theApp;
System.setProperty("javafx.preloader", FXPreloader.class.getName());
Application.launch((Class<? extends Application>)loadAndInitClass(classToRun));
}, "JavaFX BlueJ Helper").start();
try
{
methodReturn = theApp.get(3000, TimeUnit.MILLISECONDS);
}
catch (InterruptedException | TimeoutException | ExecutionException e)
{
methodReturn = null;
}
break;
case TEST_SETUP:
methodReturn = runTestSetUp(classToRun);
break;
case TEST_RUN:
methodReturn = runTestMethod(classToRun, methodToRun);
break;
case DISPOSE_WINDOWS:
disposeWindows();
break;
case LOAD_INIT_CLASS:
try {
methodReturn = loadAndInitClass(classToRun);
}
catch(Throwable cnfe) {
methodReturn = null;
}
break;
case EXIT_VM:
System.exit(0);
default:
}
}
catch(Throwable t) {
recordException(t);
}
finally {
execAction = EXIT_VM;
newThread();
}
}
};
mainThread.start();
}
private static void runOnTargetThread(RunnableThrows runnable) throws Throwable
{
if (threadToRunOn == RUN_ON_DEFAULT_THREAD)
{
runnable.run();
}
else
{
CompletableFuture<Optional<Throwable>> f = new CompletableFuture<>();
Runnable wrapped = () ->
{
try
{
runnable.run();
f.complete(Optional.empty());
}
catch (Throwable t)
{
f.complete(Optional.of(t));
}
};
if (threadToRunOn == RUN_ON_FX_THREAD)
{
SwingUtilities.invokeAndWait(() -> new JFXPanel());
Platform.runLater(wrapped);
}
else if (threadToRunOn == RUN_ON_SWING_THREAD)
{
SwingUtilities.invokeLater(wrapped);
}
else if (threadToRunOn == RUN_ON_CUSTOM_THREAD && customThreadRunner != null)
{
customThreadRunner.accept(wrapped);
}
Optional<Throwable> t = f.get();
if (t.isPresent())
{
throw t.get();
}
}
}
| Record that an exception occurred, as well as printing a filtered stack trace.
| @param t the exception which was caught
|
private static void recordException(Throwable t)
{
exception = t;
StackTraceElement [] stackTrace = t.getStackTrace();
int i;
for (i = 0; i < stackTrace.length; i++) {
if (stackTrace[i].getClassName().startsWith("__SHELL"))
break;
}
StackTraceElement [] newStackTrace = new StackTraceElement[i];
System.arraycopy(stackTrace, 0, newStackTrace, 0, i);
t.setStackTrace(newStackTrace);
t.printStackTrace();
}
| Gets an object in the scope. Used by greenfoot.
|
| @param instanceName The name of the object
| @return The object
|
public static Object getObject(String instanceName)
{
BJMap<String,Object> m = getScope(scopeId);
Object rval = null;
try {
synchronized (m) {
rval = m.get(instanceName);
if (rval == null) {
m.wait();
rval = m.get(instanceName);
}
}
}
catch (InterruptedException ie) {
}
return rval;
}
| Get the name-to-object map for the current package scope.
| Access to the map must be synchronized.
|
public static BJMap getObjectMap()
{
return getScope(scopeId);
}
| Execute user code using the custom "runLater" action.
|*/
public static void setCustomRunOnThread(Consumer<Runnable> customThreadRunner)
{
ExecServer.threadToRunOn = RUN_ON_CUSTOM_THREAD;
ExecServer.customThreadRunner = customThreadRunner;
|
|}
|
|/**
| Get the current class loader used to load user classes.
|
| @return The current class loader
|
public static ClassLoader getCurrentClassLoader()
{
return currentLoader;
}
public static class FXPreloader
extends Preloader
{
public static CompletableFuture<Application> theApp;
@Override
@OnThread(Tag.FXPlatform)
public void start(Stage primaryStage) throws Exception
{
javafx.stage.Window.getWindows().addListener(
(Change<? extends javafx.stage.Window> c) -> {
boolean anyAdded = false;
while (c.next())
anyAdded |= c.wasAdded();
}
if (anyAdded)
{
Utility.appToFront();
}
});
}
@Override
public void handleStateChangeNotification(StateChangeNotification info)
{
super.handleStateChangeNotification(info);
switch (info.getType())
{
case BEFORE_START:
theApp.complete(info.getApplication());
break;
}
}
}
}
top,
use,
map,
class ExecServer
. main
. run
. eventDispatched
. vmStarted
. vmSuspend
. showTerminalOnInput
. addWindow
. removeWindow
. getScope
. newLoader
. loadAndInitClass
. loadAllClasses
. getDeclaredInnerClasses
. addObject
. runTestSetUp
. findMethod
top,
use,
map,
class TestRecorder
. testFinished
. testFailure
. removeObject
. disposeWindows
. clearInputBuffer
top,
use,
map,
interface RunnableThrows
. run
. newThread
. run
. Application>)loadAndInitClass
. runOnTargetThread
. recordException
. getObject
. getObjectMap
. getCurrentClassLoader
top,
use,
map,
class FXPreloader
. start
. handleStateChangeNotification
1246 neLoCode
+ 95 LoComm