package bluej.debugger.jdi;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.InetAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import bluej.debugger.RunOnThread;
import bluej.utility.DialogManager;
import bluej.utility.javafx.FXPlatformSupplier;
import threadchecker.OnThread;
import threadchecker.Tag;
import bluej.Boot;
import bluej.Config;
import bluej.debugger.Debugger;
import bluej.debugger.DebuggerEvent;
import bluej.debugger.DebuggerEvent.BreakpointProperties;
import bluej.debugger.DebuggerResult;
import bluej.debugger.DebuggerTerminal;
import bluej.debugger.ExceptionDescription;
import bluej.debugger.SourceLocation;
import bluej.prefmgr.PrefMgr;
import bluej.runtime.ExecServer;
import bluej.utility.Debug;
import bluej.utility.Utility;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ArrayReference;
import com.sun.jdi.ArrayType;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.ClassLoaderReference;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.ClassObjectReference;
import com.sun.jdi.ClassType;
import com.sun.jdi.Field;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.InvocationException;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StringReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.VMMismatchException;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.Connector.Argument;
import com.sun.jdi.connect.ListeningConnector;
import com.sun.jdi.event.ExceptionEvent;
import com.sun.jdi.event.LocatableEvent;
import com.sun.jdi.event.ThreadDeathEvent;
import com.sun.jdi.event.ThreadStartEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import javafx.application.Platform;
| A class implementing the execution and debugging primitives needed by BlueJ.
|
| <p>Execution and debugging is implemented here on a second ("remote") virtual
|* machine, which gets started from here via the JDI interface.
*
* <p>The startup process is as follows:
*
* <ol>
* <li>Debugger spawns a MachineLoaderThread which begins to load the debug vm
| Any access to the debugger during this time uses getVM() which waits
| for the machine to be loaded.
| (see JdiDebugger.MachineLoaderThread).
| <li>The MachineLoaderThread creates a VMReference representing the vm. The
| VMReference in turn creates a VMEventHandler to receive events from the
| debug VM.
| <li>A "ClassPrepared" event is received telling BlueJ that the ExecServer
|* class has been loaded. At this point, breakpoints are set in certain
* places within the server class. Execution in the debug VM continues.
| <li>The ExecServer "main" method spawns two threads. One is the "server"
|* thread used to run user code. The "worker" thread is used for helper
* functions which do not execute user code paths. Both threads hit the
* breakpoints which have been set. This causes a breakpoint event to occur.
* <li>The breakpoint events are trapped. When the server thread hits the
| "vmStarted" breakpoint, the VM is considered to be started.
|* </ol>
*
* <p>We can now execute commands on the remote VM by invoking methods using the
* server thread (which is suspended at the breakpoint).
*
| <p>Non-user code used by BlueJ is run a separate "worker" thread.
|*
* @author Michael Kolling
*/
@OnThread(Tag.Any)
public class VMReference{
// the class name of the execution server class running on the remote VM
static final String SERVER_CLASSNAME = ExecServer.class.getName();
|
|// the name of the method used to suspend the ExecServer
|
|static final String SERVER_STARTED_METHOD_NAME = "vmStarted";
// the name of the method used to suspend the ExecServer
static final String SERVER_SUSPEND_METHOD_NAME = "vmSuspend";
// the name of the method used to show the terminal on input
public static final String SERVER_SHOW_TERMINAL_ON_INPUT_NAME = "showTerminalOnInput";
// A map which can be used to map instances of VirtualMachine to VMReference
private static Map<VirtualMachine, VMReference> vmToReferenceMap = new HashMap<VirtualMachine, VMReference>();
|
|// ==== instance data ====
|
|// we have a tight coupling between us and the JdiDebugger
|
|// that creates us
|
|private JdiDebugger owner = null;
|
|private DebuggerTerminal term;
|
|// The remote virtual machine and process we are referring to
|
|private VirtualMachine machine = null;
|
|// The handler for virtual machine events
|
|private VMEventHandler eventHandler = null;
|
|// the class reference to ExecServer
|
|private ClassType serverClass = null;
|
|// the thread running inside the ExecServer
|
|private ThreadReference serverThread = null;
|
|private boolean serverThreadStarted = false;
|
|// the worker thread running inside the ExecServer
|
|private ThreadReference workerThread = null;
|
|private boolean workerThreadReady = false;
|
|private boolean workerThreadReserved = false;
|
|// a record of the threads we start up for
|
|// redirecting ExecServer streams
|
|@OnThread(Tag.Any)
|
|private IOHandlerThread inputStreamRedirector = null;
|
|@OnThread(Tag.Any)
|
|private IOHandlerThread outputStreamRedirector = null;
|
|@OnThread(Tag.Any)
|
|private IOHandlerThread errorStreamRedirector = null;
|
|// the current class loader in the ExecServer
|
|private ClassLoaderReference currentLoader = null;
|
|private int exitStatus;
|
|@OnThread(Tag.Any)
|
|private ExceptionDescription lastException;
|
|/**
| Launch a remote debug VM using a TCP/IP socket.
|
| @param initDir
| the directory to have as a current directory in the remote VM
| @param libraries
| libraries to be added to the VM startup classpath
| @param mgr
| the virtual machine manager
| @return an instance of a VirtualMachine or null if there was an error
|
@OnThread(Tag.Any)
public VirtualMachine localhostSocketLaunch(File initDir, URL[] libraries, DebuggerTerminal term,
VirtualMachineManager mgr)
{
final int CONNECT_TRIES = 1;
final int CONNECT_WAIT = 500;
String [] launchParams;
Boot boot = Boot.getInstance();
List<File> filesPath = Utility.urlsToFiles(boot.getRuntimeUserClassPath());
List<File> javafxPath = Utility.urlsToFiles(boot.getJavaFXClassPath());
List<File> libraryPaths = Utility.urlsToFiles(libraries);
List<File> classPath = new ArrayList<>();
classPath.addAll(filesPath);
classPath.addAll(javafxPath);
classPath.addAll(libraryPaths);
String allClassPath = Utility.toClasspathString(classPath);
ArrayList<String> paramList = new ArrayList<String>(11);
| // Uncomment this if you want to get a command window showing
|
|// for the debug VM on Windows. Useful to let you hit Ctrl+Break and see thread dump
|
|// in case of deadlock
|
|paramList.add("cmd.exe");
paramList.add("/C");
paramList.add("start");
paramList.add("cmd.exe");
paramList.add("/K");
*/
paramList.add(Config.getJDKExecutablePath(null, "java"));
//check if any vm args are specified in Config, at the moment these
//are only Locale options: user.language and user.country
paramList.addAll(Config.getDebugVMArgs());
|
|paramList.add("-classpath");
paramList.add(allClassPath);
if (Config.isMacOS()) {
paramList.add("-Xdock:icon=" + Config.getBlueJIconPath() + "/" + Config.getVMIconsName());
paramList.add("-Xdock:name=" + Config.getVMDockName());
}
// Index for where the transport parameter is to be added
int transportIndex = paramList.size();
String streamEncoding = Config.getPropString("bluej.terminal.encoding", null);
if (streamEncoding != null) {
// Set the input/output encoding to the same as the terminal encoding, to avoid confusion
// that mismatching these two causes. See bug #509.
|
|paramList.add("-Dfile.encoding=" + streamEncoding);
}
paramList.add(SERVER_CLASSNAME);
// set output encoding if specified, default is to use system default
// this gets passed to ExecServer's main as an arg which can then be
|
|// used to specify encoding
|
|if (streamEncoding != null) {
|
|paramList.add(streamEncoding);
|
|}
|
|String transport = Config.getPropString("bluej.vm.transport", "dt_socket");
List<ListeningConnector> connectors = new ArrayList<ListeningConnector>(mgr.listeningConnectors());
// find the known connectors - order them by preference:
Iterator<ListeningConnector> it = connectors.iterator();
|
|while (it.hasNext()){
|
|ListeningConnector c = it.next();
|
|if (c.transport().name().equals(transport)) {
|
|// We've found the preferred connector
|
|it.remove();
|
|connectors.add(0, c);
|
|break;
|
|}
|
|}
|
|Throwable [] failureReasons = new Throwable[connectors.size()];
|
|for (int i = 0; i < CONNECT_TRIES; i++) {
|
|for (int j = 0; j < connectors.size(); j++) {
|
|ListeningConnector connector = connectors.get(j);
|
|try {
|
|// Set up connection arguments
|
|Map<String, Argument> arguments = connector.defaultArguments();
|
|Connector.Argument timeoutArg = arguments.get("timeout");
if (timeoutArg != null) {
// The timeout appears to be in milliseconds.
// The default is apparently no timeout.
String timeOutVal = Config.getPropString("bluej.vm.connect.timeout", "10000");
timeoutArg.setValue(timeOutVal);
}
// Make sure the local address is localhost, not the
// machine name, as using the machine name causes problems on some systems
|
|// when the network is disconnected (because the machine name binds to
|
|// the network IP, not to localhost):
|
|String listenAddress = null;
|
|if (connector.transport().name().equals("dt_socket") && arguments.containsKey("localAddress"))
{
listenAddress = InetAddress.getByName(null).getHostAddress();
arguments.get("localAddress").setValue(listenAddress);
}
// Listening connectors can only listen on one address at a time -
// Synchronize to prevent problems.
|
|synchronized (connector) {
|
|String address = connector.startListening(arguments);
|
|if (listenAddress != null) {
|
|// It seems the address name returned by connector.startListening(...) may be the host name,
|
|// even though we specifically asked for localhost. So here we'll force it to the localhost
|
|// IP address:
|
|int colonIndex = address.lastIndexOf(':');
|
|if (colonIndex != -1) {
|
|address = listenAddress + address.substring(colonIndex);
|
|}
|
|}
|
|Debug.log("" + System.currentTimeMillis() + ": Listening for JDWP connection on address: " + address);
paramList.add(transportIndex, "-agentlib:jdwp=transport=" + connector.transport().name()
+ ",address=" + address);
launchParams = paramList.toArray(new String[paramList.size()]);
paramList.remove(transportIndex);
final Process remoteVMprocess;
|
|try {
|
|remoteVMprocess = launchVM(initDir, launchParams);
|
|}
|
|catch (Throwable t) {
|
|connector.stopListening(arguments);
|
|throw t;
|
|}
|
|try {
|
|machine = connector.accept(arguments);
|
|redirectToTerminal(term, remoteVMprocess, streamEncoding);
|
|}
|
|catch (Throwable t) {
|
|// failed to connect.
|
|closeIO();
|
|try {
|
|// Ask for the exit value, since that allows us to test
|
|// whether the process has already exited.
|
|int exitCode = remoteVMprocess.exitValue();
|
|Debug.log("" + System.currentTimeMillis() + ": remote VM process has prematurely terminated with exit code: " + exitCode);
drainOutput(remoteVMprocess);
}
catch (IllegalThreadStateException itse) {
}
remoteVMprocess.destroy();
|
|throw t;
|
|}
|
|finally {
|
|connector.stopListening(arguments);
|
|}
|
|}
|
|Debug.log("Connected to debug VM via " + connector.transport().name() + " transport...");
setupEventHandling();
if (waitForStartup()) {
Debug.log("Communication with debug VM fully established.");
return machine;
}
else {
Debug.log("Error: Debug VM not signalling startup.");
}
}
catch(Throwable t) {
failureReasons[j] = t;
}
}
// Do a small wait between connection attempts
|
|try {
|
|if (i != CONNECT_TRIES - 1) {
|
|Thread.sleep(CONNECT_WAIT);
|
|}
|
|}
|
|catch (InterruptedException ie) { break;
|
|}
|
|}
|
|// failed to connect
|
|Writer dbgStream = Debug.getDebugStream();
|
|synchronized (dbgStream) {
|
|Debug.message("" + System.currentTimeMillis() + ": Failed to connect to debug VM. Reasons follow:");
for (int i = 0; i < connectors.size(); i++) {
Debug.message(connectors.get(i).transport().name() + " transport:");
PrintWriter pw = new PrintWriter(dbgStream);
failureReasons[i].printStackTrace(pw);
pw.flush();
}
}
NetworkTest.doTest();
|
|return null;
|
|}
|
|/**
| Read and log anything that the remote VM process output before it died.
|
private void drainOutput(Process remoteVMprocess)
{
InputStreamReader stdout = new InputStreamReader(remoteVMprocess.getInputStream());
char charBuf[] = new char[2048];
try {
int numRead = stdout.read(charBuf);
if (numRead != -1) {
String output = new String(charBuf, 0, numRead);
Debug.message("Output from remote process stdout: " + output);
}
InputStreamReader stderr = new InputStreamReader(remoteVMprocess.getErrorStream());
numRead = stderr.read(charBuf);
if (numRead != -1) {
String output = new String(charBuf, 0, numRead);
Debug.message("Output from remote process stderr: " + output);
}
}
catch (IOException ioe) {
Debug.message("IOException while trying to draing stdout/stderr of remote process: " + ioe.getMessage());
}
}
private void setupEventHandling()
{
EventRequestManager erm = machine.eventRequestManager();
erm.createExceptionRequest(null, false, true).enable();
erm.createClassPrepareRequest().enable();
EventRequest tsr = erm.createThreadStartRequest();
tsr.setSuspendPolicy(EventRequest.SUSPEND_NONE);
tsr.enable();
tsr = erm.createThreadDeathRequest();
tsr.setSuspendPolicy(EventRequest.SUSPEND_NONE);
tsr.enable();
eventHandler = new VMEventHandler(this, machine);
}
| Launch the debug VM and set up the I/O connectors to the terminal.
| @param initDir the directory which the vm should be started in
| @param params the parameters (including executable as first param)
| @param line a buffer which receives the first line of output from
| the debug vm process
| @param term the terminal to connect to process I/O
|
@OnThread(Tag.Any)
private Process launchVM(File initDir, String [] params)
throws IOException
{
Process vmProcess = Runtime.getRuntime().exec(params, null, initDir);
BufferedReader bro = new BufferedReader(new InputStreamReader(vmProcess.getInputStream()));
BufferedReader bre = new BufferedReader(new InputStreamReader(vmProcess.getErrorStream()));
try {
StringBuffer extraOut = new StringBuffer();
StringBuffer extraErr = new StringBuffer();
char [] buf = new char[1024];
Thread.sleep(200);
for (int i = 0; i < 5; i++) {
Thread.sleep(200);
boolean keepReading = false;
if (bro.ready()) {
int len = bro.read(buf);
if (len != -1) {
extraOut.append(buf, 0, len);
}
keepReading = true;
}
if (bre.ready()) {
int len = bre.read(buf);
if (len != -1) {
extraErr.append(buf, 0, len);
}
keepReading = true;
}
if (! keepReading) {
break;
}
}
if (extraOut.length() != 0) {
Debug.message("Extra output from debug VM on launch:" + extraOut);
}
if (extraErr.length() != 0) {
Debug.message("Error output from debug VM on launch:" + extraErr);
}
}
catch (InterruptedException ie) {
}
return vmProcess;
}
| Redirect input, output and error streams of the remote process to the terminal.
|
@OnThread(Tag.Any)
private void redirectToTerminal(DebuggerTerminal term, Process vmProcess, String streamEncoding) throws UnsupportedEncodingException
{
Reader errorReader = null;
Reader outReader = null;
Writer inputWriter = null;
if (streamEncoding == null) {
errorReader = new InputStreamReader(vmProcess.getErrorStream());
outReader = new InputStreamReader(vmProcess.getInputStream());
inputWriter = new OutputStreamWriter(vmProcess.getOutputStream());
}
else {
errorReader = new InputStreamReader(vmProcess.getErrorStream(), streamEncoding);
outReader = new InputStreamReader(vmProcess.getInputStream(), streamEncoding);
inputWriter = new OutputStreamWriter(vmProcess.getOutputStream(), streamEncoding);
}
errorStreamRedirector = redirectIOStream(errorReader, term.getErrorWriter());
outputStreamRedirector = redirectIOStream(outReader, term.getWriter());
inputStreamRedirector = redirectIOStream(term.getReader(), inputWriter);
}
| Create the second virtual machine and start the execution server (class
| ExecServer) on that machine.
|
@OnThread(Tag.Any)
public VMReference(JdiDebugger owner, DebuggerTerminal term, File initialDirectory, URL[] libraries)
throws JdiVmCreationException
{
this.owner = owner;
this.term = term;
machine = localhostSocketLaunch(initialDirectory, libraries, term, Bootstrap.virtualMachineManager());
if (machine == null) {
throw new JdiVmCreationException();
}
vmToReferenceMap.put(machine, this);
}
| Wait for all our virtual machine initialisation to occur.
|
public synchronized boolean waitForStartup()
{
serverThreadStartWait();
if (! setupServerConnection(machine)) {
return false;
}
return true;
}
| Close down this virtual machine.
|
@OnThread(Tag.Any)
public synchronized void close()
{
if (machine != null) {
closeIO();
try {
setStaticFieldValue(serverClass, ExecServer.WORKER_ACTION_NAME, machine.mirrorOf(ExecServer.EXIT_VM));
machine.dispose();
}
catch(VMDisconnectedException vmde) {
}
}
}
| Close I/O redirectors.
|
public void closeIO()
{
if (inputStreamRedirector != null) {
inputStreamRedirector.close();
inputStreamRedirector.interrupt();
}
if (errorStreamRedirector != null) {
errorStreamRedirector.close();
errorStreamRedirector.interrupt();
}
if (outputStreamRedirector != null) {
outputStreamRedirector.close();
outputStreamRedirector.interrupt();
}
}
| This method is called by the VMEventHandler when the execution server
| class (ExecServer) has been loaded into the VM. We use this to set a
| breakpoint in the server class. This is really still part of the
| initialisation process.
|
void serverClassPrepared()
{
EventRequestManager erm = machine.eventRequestManager();
List<ClassPrepareRequest> list = erm.classPrepareRequests();
erm.deleteEventRequests(list);
try {
serverClass = (ClassType) findClassByName(SERVER_CLASSNAME, null);
}
catch (ClassNotFoundException cnfe) {
throw new IllegalStateException("can't find class " + SERVER_CLASSNAME + " in debug virtual machine");
}
serverClassAddBreakpoints();
}
private Location findMethodLocation(ReferenceType classType, String methodName)
{
Method method = findMethodByName(classType, methodName);
if (method == null) {
throw new IllegalStateException("can't find method " + classType.name() + "."
+ methodName);
}
return method.location();
}
| This breakpoint is used to stop the server process to make it wait for
| our task signals. (We later use the suspended process to perform our task
| requests.)
|
private void serverClassAddBreakpoints()
{
EventRequestManager erm = machine.eventRequestManager();
BreakpointRequest serverBreakpoint = erm.createBreakpointRequest(findMethodLocation(serverClass, SERVER_STARTED_METHOD_NAME));
serverBreakpoint.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
serverBreakpoint.putProperty(SERVER_STARTED_METHOD_NAME, "yes");
serverBreakpoint.putProperty(VMEventHandler.DONT_RESUME, "yes");
serverBreakpoint.putProperty(Debugger.PERSIST_BREAKPOINT_PROPERTY, "yes");
serverBreakpoint.enable();
}
BreakpointRequest workerBreakpoint = erm.createBreakpointRequest(findMethodLocation(serverClass, SERVER_SUSPEND_METHOD_NAME));
workerBreakpoint.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
workerBreakpoint.putProperty(SERVER_SUSPEND_METHOD_NAME, "yes");
workerBreakpoint.putProperty(VMEventHandler.DONT_RESUME, "yes");
workerBreakpoint.putProperty(Debugger.PERSIST_BREAKPOINT_PROPERTY, "yes");
workerBreakpoint.enable();
}
BreakpointRequest serverBreakpoint = erm.createBreakpointRequest(findMethodLocation(serverClass, SERVER_SHOW_TERMINAL_ON_INPUT_NAME));
serverBreakpoint.setSuspendPolicy(EventRequest.SUSPEND_NONE);
serverBreakpoint.putProperty(SERVER_SHOW_TERMINAL_ON_INPUT_NAME, "yes");
serverBreakpoint.putProperty(Debugger.PERSIST_BREAKPOINT_PROPERTY, "yes");
serverBreakpoint.enable();
}
}
| Find the components on the remote VM that we need to talk to it: the
| execServer object, the performTaskMethod, and the serverThread. These
| three variables (mirrors to the remote entities) are set up here. This
| needs to be done only once.
|
private boolean setupServerConnection(VirtualMachine vm)
{
if (serverClass == null) {
Debug.reportError("server class not initialised!");
return false;
}
workerThread = (ThreadReference) getStaticFieldObject(serverClass, ExecServer.WORKER_THREAD_NAME);
if (serverThread == null || workerThread == null) {
Debug.reportError("Cannot find fields on remote VM");
return false;
}
return true;
}
| Instruct the remote machine to construct a new class loader and return its
| reference.
|
| May throw VMDisconnectedException.
|
| @param urls the classpath as an array of URL
|
@OnThread(Tag.Any)
ClassLoaderReference newClassLoader(URL [] urls)
{
synchronized(workerThread) {
workerThreadReadyWait();
workerThreadReserved = true;
setStaticFieldValue(serverClass, ExecServer.WORKER_ACTION_NAME, machine.mirrorOf(ExecServer.NEW_LOADER));
StringBuffer newcpath = new StringBuffer(200);
for (int index = 0; index < urls.length; index++) {
newcpath.append ( urls[index].toString());
newcpath.append ('\n');
}
setStaticFieldObject(serverClass, ExecServer.CLASSPATH_NAME, newcpath.toString());
workerThreadReady = false;
workerThread.resume();
workerThreadFinishWait();
currentLoader = (ClassLoaderReference) getStaticFieldObject(serverClass, ExecServer.WORKER_RETURN_NAME);
workerThreadReserved = false;
workerThread.notify();
return currentLoader;
}
}
| Get an ObjectReference mirroring a String. May throw
| VMDisconnectedException, VMOutOfMemoryException.
|
| @param value The string to mirror on the remote VM.
| @return The mirror object
|
public StringReference getMirror(String value)
{
return machine.mirrorOf(value);
}
| Load a class in the remote machine and return its reference. Note that
| this function never returns null.
|
| @return a Reference to the class mirrored in the remote VM
| @throws ClassNotFoundException if the remote class can't be loaded
|
p.public ReferenceType loadClass(String className)
throws ClassNotFoundException
{
ReferenceType rt = loadClass(className, null);
if (rt == null) {
throw new ClassNotFoundException(className);
}
return rt;
}
| Load a class in the remote VM using the given class loader.
| @param className The name of the class to load
| @param clr The remote classloader reference to use, or null to use
| the current established project classloader
| @return A reference to the loaded class, or null if the class could not be loaded.
|
p.public ReferenceType loadClass(String className, ClassLoaderReference clr)
{
synchronized(workerThread) {
workerThreadReadyWait();
workerThreadReserved = true;
setStaticFieldValue(serverClass, ExecServer.CLASSLOADER_NAME, clr);
setStaticFieldValue(serverClass, ExecServer.WORKER_ACTION_NAME, machine.mirrorOf(ExecServer.LOAD_CLASS));
setStaticFieldObject(serverClass, ExecServer.CLASSNAME_NAME, className);
workerThreadReady = false;
workerThread.resume();
workerThreadFinishWait();
ClassObjectReference robject = (ClassObjectReference) getStaticFieldObject(serverClass, ExecServer.WORKER_RETURN_NAME);
workerThreadReserved = false;
workerThread.notify();
if (robject == null) {
return null;
}
return robject.reflectedType();
}
}
| Load and initialize a class in the remote machine, and return a reference to it.
| Initialization causes static initializer assignments and blocks to be executed in
| the remote machine. This method will not return until all such blocks have completed
| execution.
|
| @param className The name of the class to load
| @return A reference to the class
| @throws ClassNotFoundException If the class could not be found
|
p.public ReferenceType loadInitClass(String className)
throws ClassNotFoundException
{
try {
serverThreadStartWait();
setStaticFieldObject(serverClass, ExecServer.CLASS_TO_RUN_NAME, className);
setStaticFieldValue(serverClass, ExecServer.EXEC_ACTION_NAME, machine.mirrorOf(ExecServer.LOAD_INIT_CLASS));
serverThreadStarted = false;
resumeServerThread();
serverThreadStartWait();
ClassObjectReference rval = (ClassObjectReference) getStaticFieldObject(serverClass, ExecServer.METHOD_RETURN_NAME);
if (rval == null)
throw new ClassNotFoundException("Remote class not found: " + className);
ObjectReference exception = getStaticFieldObject(serverClass, ExecServer.EXCEPTION_NAME);
if (exception != null) {
exceptionEvent(new InvocationException(exception));
}
return rval.reflectedType();
}
catch (VMDisconnectedException vde) {
throw new ClassNotFoundException("Remote class not loaded due to VM termination.");
}
}
| "Start" a class (i.e. invoke its main method)
|*
* @param loader
* the class loader to use
* @param classname
* the class to start
* @param eventParam
* when a BlueJEvent is generated for a breakpoint, this
| parameter is passed as the event parameter
|
public DebuggerResult runShellClass(String className)
{
try {
exitStatus = Debugger.NORMAL_EXIT;
serverThreadStartWait();
setStaticFieldObject(serverClass, ExecServer.CLASS_TO_RUN_NAME, className);
setStaticFieldValue(serverClass, ExecServer.EXEC_ACTION_NAME, machine.mirrorOf(ExecServer.EXEC_SHELL));
serverThreadStarted = false;
resumeServerThread();
serverThreadStartWait();
ObjectReference rval = getStaticFieldObject(serverClass, ExecServer.METHOD_RETURN_NAME);
if (rval == null) {
ObjectReference exception = getStaticFieldObject(serverClass, ExecServer.EXCEPTION_NAME);
if (exception != null) {
exceptionEvent(new InvocationException(exception));
return new DebuggerResult(lastException);
}
}
ObjectReference objR = getStaticFieldObject(serverClass, ExecServer.METHOD_RETURN_NAME);
return new DebuggerResult(JdiObject.getDebuggerObject(objR));
}
catch (VMDisconnectedException e) {
exitStatus = Debugger.TERMINATED;
return new DebuggerResult(exitStatus);
}
catch (Exception e) {
Debug.reportError("starting shell class failed: " + e);
e.printStackTrace();
exitStatus = Debugger.EXCEPTION;
lastException = new ExceptionDescription("Internal BlueJ error: unexpected exception in remote VM\n" + e);
}
return new DebuggerResult(lastException);
}
| Invoke the default constructor for some class, and return the resulting object.
|
public DebuggerResult instantiateClass(String className)
{
ObjectReference obj = null;
exitStatus = Debugger.NORMAL_EXIT;
try {
obj = invokeConstructor(className);
}
catch (VMDisconnectedException e) {
exitStatus = Debugger.TERMINATED;
return new DebuggerResult(Debugger.TERMINATED);
}
catch (Exception e) {
Debug.reportError("starting shell class failed: " + e);
e.printStackTrace();
exitStatus = Debugger.EXCEPTION;
lastException = new ExceptionDescription("Internal BlueJ error: unexpected exception in remote VM\n" + e);
}
if (obj == null) {
return new DebuggerResult(lastException);
}
else {
ObjectReference objFinal = obj;
return new DebuggerResult(JdiObject.getDebuggerObject(objFinal));
}
}
| Invoke a particular constructor with arguments. The parameter types
| of the constructor must be supplied (String[]) as well as the
| argument values (ObjectReference []).
|
| @param className The name of the class to construct an instance of
| @param paramTypes The parameter types of the constructor (class names)
| @param args The argument values to use in the constructor call
|
| @return The newly constructed object (or null if error/exception
| occurs)
|
public DebuggerResult instantiateClass(String className, String [] paramTypes, ObjectReference [] args)
{
ObjectReference obj = null;
exitStatus = Debugger.NORMAL_EXIT;
try {
obj = invokeConstructor(className, paramTypes, args);
}
catch (VMDisconnectedException e) {
exitStatus = Debugger.TERMINATED;
return new DebuggerResult(exitStatus);
}
catch (Exception e) {
Debug.reportError("starting shell class failed: " + e);
e.printStackTrace();
exitStatus = Debugger.EXCEPTION;
lastException = new ExceptionDescription("Internal BlueJ error: unexpected exception in remote VM\n" + e);
}
if (obj == null) {
return new DebuggerResult(lastException);
}
else {
ObjectReference objFinal = obj;
return new DebuggerResult(JdiObject.getDebuggerObject(objFinal));
}
}
| Emit a thread halted/resumed event for the given thread.
|
@OnThread(Tag.Any)
public void emitThreadEvent(JdiThread thread, boolean halted)
{
eventHandler.emitThreadEvent(thread, halted);
}
| Return the status of the last invocation. One of (NORMAL_EXIT,
| FORCED_EXIT, EXCEPTION, TERMINATED).
|
| (?? Question: What is the difference between "FORCED_EXIT" and
|* "TERMINATED"? We only seem to use the latter -dm)
*/
public int getExitStatus()
{
return exitStatus;
}
/**
* Return the text of the last exception.
*/
public ExceptionDescription getException()
|
|{
|
|return lastException;
|
|}
|
|/**
| The VM has reached its startup point.
|
public void vmStartEvent(VMStartEvent vmse)
{
serverThreadStarted = false;
}
| The VM has been disconnected or ended.
|
public void vmDisconnectEvent()
{
synchronized (this) {
owner.vmDisconnect();
exitStatus = Debugger.TERMINATED;
if (!serverThreadStarted) {
notifyAll();
}
}
if (workerThread != null) {
synchronized (workerThread) {
if (!workerThreadReady)
workerThread.notifyAll();
}
}
synchronized (vmToReferenceMap) {
vmToReferenceMap.remove(machine);
}
}
| A thread has started.
|
public void threadStartEvent(ThreadStartEvent tse)
{
owner.threadStart(tse.thread());
}
| A thread has died.
|
public void threadDeathEvent(ThreadDeathEvent tde)
{
ThreadReference tr = tde.thread();
owner.threadDeath(tr);
}
| A thread has been suspended (due to a breakpoint, step, or
| call to DebuggerThread.halt()).
|
public void threadHaltedEvent(JdiThread thread)
{
owner.threadHalted(thread);
}
| A thread has been resumed.
|
public void threadResumedEvent(JdiThread thread)
{
owner.threadResumed(thread);
}
| An exception has occurred in a thread.
|
| It doesn't really make sense to do anything here. Any exception which occurs
| in the primary execution thread does not come through here.
|
public void exceptionEvent(ExceptionEvent exc)
{
}
| Invoke an arbitrary method on an object, using the worker thread.
| If the called method exits via an exception, this method returns null.
|
| @param o The object to invoke the method on
| @param m The method to invoke
| @param args The arguments to pass to the method (List of Values)
| @return The return Value from the method
|
private Value safeInvoke(ObjectReference o, Method m, List<? extends Value> args)
{
synchronized (workerThread) {
workerThreadReadyWait();
Value v = null;
try {
v = o.invokeMethod(workerThread, m, args, ObjectReference.INVOKE_SINGLE_THREADED);
}
catch (ClassNotLoadedException cnle) {
}
catch (InvalidTypeException ite) {
}
catch (IncompatibleThreadStateException itse) {
}
catch (InvocationException ie) {
}
return v;
}
}
public void exceptionEvent(InvocationException exc)
{
List<Value> empty = new LinkedList<Value>();
ObjectReference remoteException = exc.exception();
Field msgField = remoteException.referenceType().fieldByName("detailMessage");
StringReference msgVal = (StringReference) remoteException.getValue(msgField);
String exceptionText = (msgVal == null ? null : msgVal.value());
String excClass = exc.exception().type().name();
ReferenceType remoteType = exc.exception().referenceType();
List<Method> getStackTraceMethods = remoteType.methodsByName("getStackTrace");
Method getStackTrace = (Method)getStackTraceMethods.get(0);
ArrayReference stackValue = (ArrayReference)safeInvoke(exc.exception(), getStackTrace, empty);
ObjectReference [] stackt = (ObjectReference [])stackValue.getValues().toArray(new ObjectReference[0]);
List<SourceLocation> stack = new LinkedList<SourceLocation>();
if (stackt.length > 0) {
ReferenceType StackTraceElementType = (ReferenceType)stackt[0].type();
Method getClassName = (Method)StackTraceElementType.methodsByName("getClassName").get(0);
Method getFileName = (Method)StackTraceElementType.methodsByName("getFileName").get(0);
Method getLineNum = (Method)StackTraceElementType.methodsByName("getLineNumber").get(0);
Method getMethodName = (Method)StackTraceElementType.methodsByName("getMethodName").get(0);
for (int i = 0; i < stackt.length; i++) {
Value classNameV = safeInvoke(stackt[i], getClassName, empty);
Value fileNameV = safeInvoke(stackt[i], getFileName, empty);
Value methodNameV = safeInvoke(stackt[i], getMethodName, empty);
Value lineNumV = safeInvoke(stackt[i], getLineNum, empty);
String className = ((StringReference)classNameV).value();
String fileName = null;
if (fileNameV != null) {
fileName = ((StringReference)fileNameV).value();
}
String methodName = ((StringReference)methodNameV).value();
int lineNumber = ((IntegerValue)lineNumV).value();
stack.add(new SourceLocation(className,fileName,methodName,lineNumber));
}
}
exitStatus = Debugger.EXCEPTION;
lastException = new ExceptionDescription(excClass, exceptionText, stack);
}
| A breakpoint has been hit or step completed in a thread.
|
public void breakpointEvent(LocatableEvent event, int debuggerEventType, boolean skipUpdate)
{
if (event.request().getProperty(SERVER_STARTED_METHOD_NAME) != null) {
synchronized (this) {
serverThreadStarted = true;
serverThread = event.thread();
owner.raiseStateChangeEvent(Debugger.IDLE);
notifyAll();
}
}
else if (event.request().getProperty(SERVER_SUSPEND_METHOD_NAME) != null) {
if (workerThread == null) {
workerThread = event.thread();
}
synchronized (workerThread) {
workerThreadReady = true;
workerThread.notifyAll();
}
}
else if (event.request().getProperty(SERVER_SHOW_TERMINAL_ON_INPUT_NAME) != null) {
this.term.showOnInput();
}
else {
if (serverThread.equals(event.thread())) {
owner.raiseStateChangeEvent(Debugger.SUSPENDED);
}
Location location = event.location();
String className = location.declaringType().name();
String fileName;
try {
fileName = location.sourceName();
}
catch (AbsentInformationException e) {
fileName = null;
}
if (fileName != null && fileName.startsWith("__SHELL")
|| className != null && className.startsWith("bluej.runtime."
|
event.thread().resume();
return;
}
owner.breakpoint(event.thread(), debuggerEventType, skipUpdate, makeBreakpointProperties(event.request()));
}
}
private BreakpointProperties makeBreakpointProperties(final EventRequest request)
{
if (request == null)
return null;
else{ return new DebuggerEvent.BreakpointProperties() {
@OnThread(Tag.Any)
public Object get(Object key){
return request.getProperty(key);
}
}
};
}
public boolean screenBreakpointEvent(LocatableEvent event, int debuggerEventType)
{
BreakpointProperties props = makeBreakpointProperties(event.request());
for (String special : Arrays.asList(
SERVER_STARTED_METHOD_NAME,
SERVER_SUSPEND_METHOD_NAME,
SERVER_SHOW_TERMINAL_ON_INPUT_NAME))
{
if (props.get(special) != null)
{
return true;
}
}
return owner.screenBreakpoint(event.thread(), debuggerEventType, props);
}
| Find and load all classes declared in the same source file as className
| and then find the Location object for the source at the line 'line'.
|
private Location loadClassesAndFindLine(String className, int line)
{
ReferenceType remoteClass = null;
try {
remoteClass = loadClass(className);
}
catch (ClassNotFoundException cnfe) {
return null;
}
List<ReferenceType> allTypesInFile = new ArrayList<ReferenceType>();
buildNestedTypes(remoteClass, allTypesInFile);
Iterator<ReferenceType> it = allTypesInFile.iterator();
while (it.hasNext()){
ReferenceType r = it.next();
try {
List<Location> list = r.locationsOfLine(line);
if (list.size() > 0)
return (Location) list.get(0);
}
catch (AbsentInformationException aie) {
}
}
return null;
}
| Recursively construct a list of all Types started with rootType and
| including all its nested types.
|
| @param rootType
| the root to start building at
| @param l
| the List to add the reference types to
|
private void buildNestedTypes(ReferenceType rootType, List<ReferenceType> l)
{
try {
synchronized(workerThread) {
workerThreadReadyWait();
workerThreadReserved = true;
setStaticFieldValue(serverClass, ExecServer.WORKER_ACTION_NAME, machine.mirrorOf(ExecServer.LOAD_ALL));
setStaticFieldObject(serverClass, ExecServer.CLASSNAME_NAME, rootType.name());
workerThreadReady = false;
workerThread.resume();
workerThreadFinishWait();
ObjectReference or = getStaticFieldObject(serverClass, ExecServer.WORKER_RETURN_NAME);
workerThreadReserved = false;
workerThread.notify();
ArrayReference inners = (ArrayReference) or;
Iterator<Value> i = inners.getValues().iterator();
while (i.hasNext()){
ClassObjectReference cor = (ClassObjectReference) i.next();
ReferenceType rt = cor.reflectedType();
if (rt.isPrepared()) {
l.add(rt);
}
}
}
}
catch (VMDisconnectedException vmde) {
}
catch (VMMismatchException vmmme) {
}
}
| Set a breakpoint at a specified line in a class.
|
| @param className
| The class in which to set the breakpoint.
| @param line
| The line number of the breakpoint.
| @param properties The collection of properties to set on the breakpoint. Can be null.
| @return null if there was no problem, or an error string
|
p.public String setBreakpoint(String className, int line, Map<String, String> properties)
{
Location location = loadClassesAndFindLine(className, line);
if (location == null) {
return Config.getString("debugger.jdiDebugger.noCodeMsg");
}
EventRequestManager erm = machine.eventRequestManager();
BreakpointRequest bpreq = erm.createBreakpointRequest(location);
bpreq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
bpreq.putProperty(VMEventHandler.DONT_RESUME, "yes");
if (properties != null) {
for (Map.Entry<String, String> property : properties.entrySet()) {
bpreq.putProperty(property.getKey(), property.getValue());
}
}
bpreq.enable();
return null;
}
p.public String setBreakpoint(ReferenceType classType, int line, Map<String, String> properties)
{
try {
List<Location> locations = classType.locationsOfLine(line);
if (locations.isEmpty()) {
return Config.getString("debugger.jdiDebugger.noCodeMsg");
}
setBreakpoint(locations.get(0), properties);
return null;
}
catch (AbsentInformationException aie) {
return Config.getString("debugger.jdiDebugger.noCodeMsg");
}
}
p.public void setBreakpoint(Location location, Map<String,String> properties)
{
EventRequestManager erm = machine.eventRequestManager();
BreakpointRequest bpreq = erm.createBreakpointRequest(location);
bpreq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
bpreq.putProperty(VMEventHandler.DONT_RESUME, "yes");
if (properties != null) {
for (Map.Entry<String, String> property : properties.entrySet()) {
bpreq.putProperty(property.getKey(), property.getValue());
}
}
bpreq.enable();
}
p.public String setBreakpoint(String className, String methodName, Map<String, String> properties)
{
try {
loadClass(className);
ClassType classType = (ClassType)findClassByName(className);
Location loc = findMethodLocation(classType, methodName);
return setBreakpoint(className, loc.lineNumber(), properties);
} catch (ClassNotFoundException e) {
return "Could not find class: " + className;
}
}
p.public String setBreakpoint(ReferenceType classType, String methodName, Map<String, String> properties)
{
Location loc = findMethodLocation(classType, methodName);
setBreakpoint(loc, properties);
return null;
}
| Clear all the breakpoints at a specified line in a class.
|
| @param className
| The class in which to clear the breakpoints.
| @param line
| The line number of the breakpoint.
| @return null if there was no problem, or an error string
|
p.public String clearBreakpoint(String className, int line)
{
Location location = loadClassesAndFindLine(className, line);
if (location == null) {
return Config.getString("debugger.jdiDebugger.noCodeMsg");
}
return clearBreakpoint(location);
}
p.public String clearBreakpoint(String className, String methodName)
{
try {
ClassType classType = (ClassType)findClassByName(className);
Location loc = findMethodLocation(classType, methodName);
return clearBreakpoint(loc);
} catch (ClassNotFoundException e) {
return "Could not find class: " + className;
}
}
p.public String clearBreakpoint(ReferenceType classType, String methodName)
{
Location loc = findMethodLocation(classType, methodName);
return clearBreakpoint(loc);
}
p.public String clearBreakpoint(Location location)
{
EventRequestManager erm = machine.eventRequestManager();
boolean found = false;
List<BreakpointRequest> list = erm.breakpointRequests();
for (int i = 0; i < list.size(); i++) {
BreakpointRequest bp = list.get(i);
if (bp.location().equals(location)) {
erm.deleteEventRequest(bp);
found = true;
}
}
if (found) {
return null;
}
else {
return Config.getString("debugger.jdiDebugger.noBreakpointMsg");
}
}
| Return a list of the Locations of user breakpoints in the VM.
|
public List getBreakpoints()
{
EventRequestManager erm = machine.eventRequestManager();
List<Location> breaks = new LinkedList<Location>();
List<BreakpointRequest> allBreakpoints = erm.breakpointRequests();
Iterator<BreakpointRequest> it = allBreakpoints.iterator();
while (it.hasNext()){
BreakpointRequest bp = (BreakpointRequest) it.next();
if (bp.location().declaringType().classLoader() == currentLoader) {
breaks.add(bp.location());
}
}
return breaks;
}
| Remove all user breakpoints
|
public void clearAllBreakpoints()
{
EventRequestManager erm = machine.eventRequestManager();
List<BreakpointRequest> breaks = new LinkedList<BreakpointRequest>();
List<BreakpointRequest> allBreakpoints = erm.breakpointRequests();
Iterator<BreakpointRequest> it = allBreakpoints.iterator();
while (it.hasNext()){
BreakpointRequest bp = (BreakpointRequest) it.next();
if (bp.getProperty(Debugger.PERSIST_BREAKPOINT_PROPERTY) == null) {
breaks.add(bp);
}
}
erm.deleteEventRequests(breaks);
}
| Remove all breakpoints for the given class.
|
public void clearBreakpointsForClass(String className)
{
EventRequestManager erm = machine.eventRequestManager();
List<BreakpointRequest> allBreakpoints = erm.breakpointRequests();
Iterator<BreakpointRequest> it = allBreakpoints.iterator();
List<BreakpointRequest> toDelete = new LinkedList<BreakpointRequest>();
while (it.hasNext()){
BreakpointRequest bp = it.next();
ReferenceType bpType = bp.location().declaringType();
if (bpType.name().equals(className) && bp.getProperty(Debugger.PERSIST_BREAKPOINT_PROPERTY) == null) {
toDelete.add(bp);
}
}
erm.deleteEventRequests(toDelete);
}
| Restore the previosuly saved breakpoints with the new classloader.
|
| @param loader
| The new class loader to restore the breakpoints into
|
public void restoreBreakpoints(List<Location> saved)
{
EventRequestManager erm = machine.eventRequestManager();
List<Location> newSaved = new ArrayList<Location>();
Iterator<Location> savedIterator = saved.iterator();
while (savedIterator.hasNext()){
Location oldLocation = savedIterator.next();
Location newLocation = loadClassesAndFindLine(oldLocation.declaringType().name(), oldLocation.lineNumber());
if (newLocation != null) {
newSaved.add(newLocation);
}
}
synchronized(workerThread) {
workerThreadReadyWait();
machine.suspend();
eventHandler.waitQueueEmpty();
erm.deleteAllBreakpoints();
serverClassAddBreakpoints();
Iterator<Location> it = newSaved.iterator();
while (it.hasNext()){
Location l = (Location) it.next();
BreakpointRequest bpreq = erm.createBreakpointRequest(l);
bpreq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
bpreq.putProperty(VMEventHandler.DONT_RESUME, "yes");
bpreq.enable();
}
machine.resume();
}
}
| Wait for the "server" thread to start. This must be synchronized on
|* serverThreadLock (in JdiDebugger).
*/
private void serverThreadStartWait()
{
synchronized(this) {
try {
while (!serverThreadStarted){
|
|if (exitStatus == Debugger.TERMINATED)
|
|throw new VMDisconnectedException();
|
|wait(); // wait for new thread to start
|
|}
|
|}
|
|catch (InterruptedException ie) {
|
|}
|
|}
|
|}
|
|/**
| Resume the server thread to begin executing some function.
|
| Calls to this method should be synchronized on the serverThreadLock
| (in JdiDebugger).
|
private void resumeServerThread()
{
synchronized (eventHandler) {
serverThread.resume();
owner.serverThreadResumed(serverThread);
owner.raiseStateChangeEvent(Debugger.RUNNING);
}
}
| Wait until the "worker" thread is ready for use. This method should
|* be called with the workerThread monitor held.
*
* @throws VMDisconnectedException if the VM terminates.
*/
private void workerThreadReadyWait()
{
|
|try {
|
|while (!workerThreadReady || workerThreadReserved){
|
|if (exitStatus == Debugger.TERMINATED) {
|
|throw new VMDisconnectedException();
|
|}
|
|workerThread.wait();
|
|}
|
|}
|
|catch(InterruptedException ie) {
|
|}
|
|}
|
|/**
| Wait until the "worker" thread has finished executing. This
|* should be called only if workerThreadReserved has been set to
* true by the current thread.
*
* @throws VMDisconnectedException if the VM terminates.
*/
private void workerThreadFinishWait()
|
|{
|
|try {
|
|while (!workerThreadReady){
|
|if (exitStatus == Debugger.TERMINATED) {
|
|throw new VMDisconnectedException();
|
|}
|
|workerThread.wait();
|
|}
|
|}
|
|catch(InterruptedException ie) {
|
|}
|
|}
|
|public FXPlatformSupplier<DebuggerResult> launchFXApp(String className)
|
|{
|
|ObjectReference obj = null;
|
|exitStatus = Debugger.NORMAL_EXIT;
|
|try {
|
|obj = launchFXAppHelper(className);
|
|}
|
|catch (VMDisconnectedException e) {
|
|exitStatus = Debugger.TERMINATED;
|
|// return null; // debugger state change handled elsewhere
|
|return () -> new DebuggerResult(Debugger.TERMINATED);
|
|}
|
|catch (Exception e) {
|
|// remote invocation failed
|
|Debug.reportError("Launch FX app failed: " + e);
e.printStackTrace();
exitStatus = Debugger.EXCEPTION;
lastException = new ExceptionDescription("Internal BlueJ error: unexpected exception in remote VM\n" + e);
}
if (obj == null) {
return () -> new DebuggerResult(lastException);
}
else {
ObjectReference objFinal = obj;
return () -> new DebuggerResult(JdiObject.getDebuggerObject(objFinal));
|
|}
|
|}
|
|private ObjectReference launchFXAppHelper(String className)
|
|{
|
|// Calls to this method are serialized via serverThreadLock in JdiDebugger
|
|serverThreadStartWait();
|
|// Store the class and method to call
|
|setStaticFieldObject(serverClass, ExecServer.CLASS_TO_RUN_NAME, className);
|
|setStaticFieldValue(serverClass, ExecServer.EXEC_ACTION_NAME, machine.mirrorOf(ExecServer.LAUNCH_FX_APP));
|
|// Resume the thread, wait for it to finish and the new thread to start
|
|serverThreadStarted = false;
|
|resumeServerThread();
|
|serverThreadStartWait();
|
|// Get return value and check for exceptions
|
|Value rval = getStaticFieldObject(serverClass, ExecServer.METHOD_RETURN_NAME);
|
|if (rval == null) {
|
|ObjectReference exception = getStaticFieldObject(serverClass, ExecServer.EXCEPTION_NAME);
|
|if (exception != null) {
|
|exceptionEvent(new InvocationException(exception));
|
|}
|
|}
|
|return (ObjectReference) rval;
|
|}
|
|/**
| Invoke the default constructor for the given class and return a reference
| to the generated instance.
|
private ObjectReference invokeConstructor(String className)
{
serverThreadStartWait();
setStaticFieldObject(serverClass, ExecServer.CLASS_TO_RUN_NAME, className);
setStaticFieldValue(serverClass, ExecServer.EXEC_ACTION_NAME, machine.mirrorOf(ExecServer.INSTANTIATE_CLASS));
serverThreadStarted = false;
resumeServerThread();
serverThreadStartWait();
Value rval = getStaticFieldObject(serverClass, ExecServer.METHOD_RETURN_NAME);
if (rval == null) {
ObjectReference exception = getStaticFieldObject(serverClass, ExecServer.EXCEPTION_NAME);
if (exception != null) {
exceptionEvent(new InvocationException(exception));
}
}
return (ObjectReference) rval;
}
| Invoke a particular constructor with arguments. The parameter types
| of the constructor must be supplied (String[]) as well as the
| argument values (ObjectReference []).
|
| @param className The name of the class to construct an instance of
| @param paramTypes The parameter types of the constructor (class names)
| @param args The argument values to use in the constructor call
|
| @return The newly constructed object
|
private ObjectReference invokeConstructor(String className, String [] paramTypes, ObjectReference [] args)
{
serverThreadStartWait();
boolean needsMachineResume = false;
try {
int length = paramTypes.length;
if (args.length != length) {
throw new IllegalArgumentException();
}
ArrayType objectArray = (ArrayType) loadClass("[Ljava.lang.Object;");
ArrayType stringArray = (ArrayType) loadClass("[Ljava.lang.String;");
machine.suspend();
needsMachineResume = true;
ArrayReference argsArray = objectArray.newInstance(length);
ArrayReference typesArray = stringArray.newInstance(length);
while (true){
try {
argsArray.disableCollection();
break;
}
catch (ObjectCollectedException oce) {
argsArray = objectArray.newInstance(length);
}
}
while (true){
try {
typesArray.disableCollection();
break;
}
catch (ObjectCollectedException oce) {
typesArray = stringArray.newInstance(length);
}
}
for (int i = 0; i < length; i++) {
StringReference s = machine.mirrorOf(paramTypes[i]);
typesArray.setValue(i, s);
argsArray.setValue(i, args[i]);
}
setStaticFieldValue(serverClass, ExecServer.PARAMETER_TYPES_NAME, typesArray);
setStaticFieldValue(serverClass, ExecServer.ARGUMENTS_NAME, argsArray);
typesArray.enableCollection();
argsArray.enableCollection();
setStaticFieldObject(serverClass, ExecServer.CLASS_TO_RUN_NAME, className);
setStaticFieldValue(serverClass, ExecServer.EXEC_ACTION_NAME, machine.mirrorOf(ExecServer.INSTANTIATE_CLASS_ARGS));
machine.resume();
needsMachineResume = false;
serverThreadStarted = false;
resumeServerThread();
serverThreadStartWait();
Value rval = getStaticFieldObject(serverClass, ExecServer.METHOD_RETURN_NAME);
if (rval == null) {
ObjectReference exception = getStaticFieldObject(serverClass, ExecServer.EXCEPTION_NAME);
if (exception != null) {
exceptionEvent(new InvocationException(exception));
}
}
return (ObjectReference) rval;
}
catch (ClassNotFoundException cnfe) {
}
catch (ClassNotLoadedException cnle) {
}
catch (InvalidTypeException ite) {
}
finally {
if (needsMachineResume) {
machine.resume();
}
}
return null;
}
public Value invokeTestSetup(String cl)
throws InvocationException
{
serverThreadStartWait();
setStaticFieldObject(serverClass, ExecServer.CLASS_TO_RUN_NAME, cl);
setStaticFieldValue(serverClass, ExecServer.EXEC_ACTION_NAME, machine.mirrorOf(ExecServer.TEST_SETUP));
serverThreadStarted = false;
resumeServerThread();
serverThreadStartWait();
Value rval = getStaticFieldObject(serverClass, ExecServer.METHOD_RETURN_NAME);
if (rval == null) {
ObjectReference e = getStaticFieldObject(serverClass, ExecServer.EXCEPTION_NAME);
if (e != null) {
exceptionEvent(new InvocationException(e));
throw new InvocationException(e);
}
}
return rval;
}
| Run a JUnit test on a single test method or all test methods (including setup/teardown).
| @param cl The class containing the test methods
| @return null if all tests passed, or an ArrayReference if any fails,
| which has a length of [1 + 7*(number of failures/errors)]
| The first item of the array contains the runtime of executing all tests,
| then each failure/error has seven consecutive items in the array which contains:
| [1] = the exception message (or "no exception message")<br>
|* [2] = the stack trace as a string (or "no stack trace")<br>
* [3] = the name of the class in which the exception/failure occurred<br>
* [4] = the source filename for where the exception/failure occurred<br>
* [5] = the name of the method in which the exception/failure occurred
| [6] = the line number where the exception/failure occurred (a string)
| [7] = "failure" or "error" (string)<br>with:
|* @throws InvocationException
*/
public Value invokeRunTest(String cl, String method)
throws InvocationException
{
// Calls to this method are serialized via serverThreadLock in JdiDebugger
|
|serverThreadStartWait();
|
|// Store the class and method to call
|
|setStaticFieldObject(serverClass, ExecServer.CLASS_TO_RUN_NAME, cl);
|
|setStaticFieldObject(serverClass, ExecServer.METHOD_TO_RUN_NAME, method);
|
|setStaticFieldValue(serverClass, ExecServer.EXEC_ACTION_NAME, machine.mirrorOf(ExecServer.TEST_RUN));
|
|// Resume the thread, wait for it to finish and the new thread to start
|
|serverThreadStarted = false;
|
|resumeServerThread();
|
|serverThreadStartWait();
|
|Value rval = getStaticFieldObject(serverClass, ExecServer.METHOD_RETURN_NAME);
|
|if (rval == null) {
|
|ObjectReference e = getStaticFieldObject(serverClass, ExecServer.EXCEPTION_NAME);
|
|if (e != null) {
|
|exceptionEvent(new InvocationException(e));
|
|throw new InvocationException(e);
|
|}
|
|}
|
|return rval;
|
|}
|
|/**
| Dispose of all gui windows opened from the debug vm.
|
void disposeWindows()
{
serverThreadStartWait();
setStaticFieldValue(serverClass, ExecServer.EXEC_ACTION_NAME, machine.mirrorOf(ExecServer.DISPOSE_WINDOWS));
serverThreadStarted = false;
resumeServerThread();
}
| Add an object to the object map on the debug vm.
| @param instanceName the name of the object to add
| @param object a reference to the object to add
|
p.public void addObject(String scopeId, String instanceName, ObjectReference object)
{
try {
synchronized(workerThread) {
workerThreadReadyWait();
setStaticFieldValue(serverClass, ExecServer.WORKER_ACTION_NAME, machine.mirrorOf(ExecServer.ADD_OBJECT));
setStaticFieldObject(serverClass, ExecServer.OBJECTNAME_NAME, instanceName);
setStaticFieldValue(serverClass, ExecServer.OBJECT_NAME, object);
setStaticFieldObject(serverClass, ExecServer.SCOPE_ID_NAME, scopeId);
workerThreadReady = false;
workerThread.resume();
}
}
catch (VMDisconnectedException vmde) {
}
catch (VMMismatchException vmmme) {
}
}
| Remove an object from the object map on the debug vm.
| @param instanceName the name of the object to remove
|
p.public void removeObject(String scopeId, String instanceName)
{
synchronized(workerThread) {
try {
workerThreadReadyWait();
setStaticFieldValue(serverClass, ExecServer.WORKER_ACTION_NAME, machine.mirrorOf(ExecServer.REMOVE_OBJECT));
setStaticFieldObject(serverClass, ExecServer.OBJECTNAME_NAME, instanceName);
setStaticFieldObject(serverClass, ExecServer.SCOPE_ID_NAME, scopeId);
workerThreadReady = false;
workerThread.resume();
}
catch(VMDisconnectedException vmde) {
}
}
}
| Check whether a thread is sitting on the server thread breakpoint.
|
p.public static boolean isAtMainBreakpoint(ThreadReference tr)
{
try {
return (tr.isAtBreakpoint() && SERVER_CLASSNAME.equals(tr.frame(0).location().declaringType().name()));
}
catch (IncompatibleThreadStateException e) {
return false;
}
}
| Get a reference to an object from a static field in some class in the
| debug VM.
|
| VMDisconnected exception may be thrown.
|
| @param cl The class containing the field
| @param fieldName The name of the field
| @return An ObjectReference referring to the object
|
p.public ObjectReference getStaticFieldObject(ClassType cl, String fieldName)
{
Field resultField = cl.fieldByName(fieldName);
if (resultField == null)
throw new IllegalArgumentException("getting field " + fieldName + " resulted in no fields");
return (ObjectReference) cl.getValue(resultField);
}
| Set the value of a static field in the debug VM.
| @param cl The class containing the field
| @param fieldName The name of the field
| @param value The value to which the field must be set
|
p.public void setStaticFieldValue(ClassType cl, String fieldName, Value value)
{
Field field = cl.fieldByName(fieldName);
try {
cl.setValue(field,value);
}
catch(InvalidTypeException ite) {
}
catch(ClassNotLoadedException cnle) {
}
}
| Set the value of some static field as a string. A mirror of the given
| string value is created on the debug VM.
|
p.public void setStaticFieldObject(ClassType cl, String fieldName, String value)
{
try
{
StringReference s = null;
if (value != null)
{
s = machine.mirrorOf(value);
s.disableCollection();
}
setStaticFieldValue(cl, fieldName, s);
if (value != null)
{
s.enableCollection();
}
}
catch(ObjectCollectedException oce) {
machine.suspend();
StringReference s;
if (value != null)
{
s = machine.mirrorOf(value);
setStaticFieldValue(cl, fieldName, s);
}
machine.resume();
}
}
| Find the mirror of a class/interface/array in the remote VM.
|
| The class is expected to exist. We expect only one single class to exist
| with this name. Throws a ClassNotFoundException if the class could not be
| found.
|
| This should only be used for classes that we know exist and are loaded ie
| ExecServer etc.
|
private ReferenceType findClassByName(String className, ClassLoaderReference clr)
throws ClassNotFoundException
{
List<ReferenceType> list = machine.classesByName(className);
if (list.size() == 1) {
return (ReferenceType) list.get(0);
}
else if (list.size() > 1) {
Iterator<ReferenceType> iter = list.iterator();
while (iter.hasNext()){
ReferenceType cl = iter.next();
if (cl.classLoader() == clr)
return cl;
}
}
throw new ClassNotFoundException(className);
}
| Find the mirror of a class/interface/array in the remote VM.
|
| @param className
| the name of the class to find
| @return a reference to the class
|
| @throws ClassNotFoundException
|
public ReferenceType findClassByName(String className)
throws ClassNotFoundException
{
return findClassByName(className, currentLoader);
}
| Find the mirror of a method in the remote VM.
|
| The method is expected to exist. We expect only one single method to
| exist with this name and report an error if more than one is found.
|
p.public Method findMethodByName(ReferenceType type, String methodName)
{
List<Method> list = type.methodsByName(methodName);
if (list.size() != 1) {
throw new IllegalArgumentException("getting method " + methodName + " resulted in " + list.size()
+ " methods");
}
return (Method) list.get(0);
}
| Create a thread that will retrieve any output from the remote machine and
| direct it to our terminal (or vice versa).
|
private IOHandlerThread redirectIOStream(final Reader reader, final Writer writer)
{
IOHandlerThread thr;
thr = new IOHandlerThread(reader, writer);
thr.setPriority(Thread.MAX_PRIORITY - 1);
thr.start();
return thr;
}
| Sets which field future constructor/method invocations will run on.
|
| Communicates the change to the debug VM, but obviously will only affect
| future methods, not any already running.
|
public void setRunOnThread(RunOnThread runOnThread)
{
if (Config.isGreenfoot())
return;
int fieldValue;
switch (runOnThread)
{
case FX:
fieldValue = ExecServer.RUN_ON_FX_THREAD;
break;
case SWING:
fieldValue = ExecServer.RUN_ON_SWING_THREAD;
break;
default:
fieldValue = ExecServer.RUN_ON_DEFAULT_THREAD;
break;
}
synchronized(workerThread)
{
workerThreadReadyWait();
setStaticFieldValue(serverClass, ExecServer.RUN_ON_THREAD_NAME, machine.mirrorOf(fieldValue));
}
}
| The thread for retrieving output from the remote machine and redirecting
| it to the terminal.
|
private class IOHandlerThread
extends Thread
{
private Reader reader;
private Writer writer;
private volatile boolean keepRunning = true;
@OnThread(Tag.Any)
IOHandlerThread(Reader reader, Writer writer)
{
super("BlueJ I/O Handler");
this.reader = reader;
this.writer = writer;
setPriority(Thread.MIN_PRIORITY);
}
@OnThread(Tag.Any)
public void close()
{
keepRunning = false;
}
public void run()
{
try {
char [] chbuf = new char[4096];
while (keepRunning){
int numchars = reader.read(chbuf);
if (numchars == -1) {
keepRunning = false;
}
else if (keepRunning) {
writer.write(chbuf, 0, numchars);
if (! reader.ready()) {
writer.flush();
}
}
}
}
catch (IOException ex) {
}
}
}
| Find the VMReference which corresponds to the supplied VirtualMachine instance.
|
public static VMReference getVmForMachine(VirtualMachine mc)
{
synchronized (vmToReferenceMap) {
return (VMReference) vmToReferenceMap.get(mc);
}
}
|
|
|
public void dumpConnectorArgs(Map arguments)
|
|
{} // debug code to print out all existing arguments and their
|
|
// description
|
|
Collection c = arguments.values();
|
|
Iterator i = c.iterator();
|
|
while (i.hasNext()) {}Connector.Argument a = (Connector.Argument) i.next();
|
|
Debug.message("arg name: " + a.name());
Debug.message(" descr: " + a.description());
Debug.message(" value: " + a.value());
}
}
*/
}