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