package bluej.debugger.jdi;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import bluej.BlueJEvent;
import bluej.BlueJEventListener;
import bluej.debugger.*;
import bluej.pkgmgr.Project;
import bluej.utility.javafx.FXPlatformSupplier;
import com.sun.jdi.*;
import threadchecker.OnThread;
import threadchecker.Tag;
import bluej.Config;
import bluej.classmgr.BPClassLoader;
import bluej.debugmgr.Invoker;
import bluej.utility.Debug;
import bluej.utility.JavaNames;
| A class implementing the execution and debugging primitives needed by BlueJ.
|
| <p>This class is tightly coupled with the classes VMReference and
| VMEventHandler. JdiDebugger is the half of the debugger that is persistent
| across debugger sessions. VMReference and VMEventHandler will be constructed
| anew each time a remote VM is started. VMReference handles most of the work
| of making the remote VM do things. VMEventHandler starts a new thread that
| listens for remote VM events and calls back into VMReference on reciept of
| these events.
|
| <p>Most of the actual access to the virtual machine occurs through the
| MachineLoader thread. When the vm is restarted by user request, a new loader
| thread is created immediately so that any method calls/etc will execute on
| the new machine (after waiting until it has loaded).
|
| @author Michael Kolling
| @author Andrew Patterson
|
public class JdiDebugger
extends Debugger{
private static final int loaderPriority = Thread.NORM_PRIORITY - 2;
@OnThread(Tag.Any)
private boolean autoRestart = true;
@OnThread(Tag.Any)
private boolean selfRestart = false;
| The reference to the current remote VM handler. Will be null if the remote VM is not
| currently running.
|
@OnThread(Tag.Any)
private VMReference vmRef;
@OnThread(Tag.Any)
private MachineLoaderThread machineLoader;
| An object to provide a lock for server thread execution
|
@OnThread(Tag.Any)
private Object serverThreadLock = new Object();
@OnThread(Tag.Any)
private JdiThreadSet allThreads;
private final DebuggerThreadListener threadListener;
@OnThread(Tag.Any)
private final List<DebuggerListener> listenerList = new ArrayList<DebuggerListener>();
private File startingDirectory;
private DebuggerTerminal terminal;
@OnThread(value = Tag.Any, requireSynchronized = true)
private Set<String> usedNames;
| Current machine state. This is changed only by the VM event queue (see VMEventHandler),
| but write access is also protected by the listener list mutex. This makes it possible to
| add a listener and know the state at the time the listener was added. Furthermore the
| state will only be set to RUNNING while the server thread lock is also held.
|
@OnThread(Tag.Any)
private int machineState = NOTREADY;
private BPClassLoader lastProjectClassLoader;
private ExceptionDescription lastException;
| User libraries to be added to VM classpath
|
private URL[] libraries = {
};
private RunOnThread runOnThread = RunOnThread.DEFAULT;
| Construct an instance of the debugger.
|
| <p>This constructor should not be used by the main part of BlueJ. Access
| should be through Debugger.getDebuggerImpl().
|
| @param startingDirectory
| a File representing the directory we should launch the debug
| VM in.
| @param terminal
| a Terminal where we can do input/output.
|
public JdiDebugger(File startingDirectory, DebuggerTerminal terminal, DebuggerThreadListener debuggerThreadListener)
{
this.startingDirectory = startingDirectory;
this.terminal = terminal;
this.threadListener = debuggerThreadListener;
allThreads = new JdiThreadSet();
usedNames = new TreeSet<String>();
}
@Override
public void setUserLibraries(URL[] libraries)
{
this.libraries = libraries;
}
| Start debugging.
|
@Override
@OnThread(Tag.Any)
public synchronized void launch()
{
if (vmRef != null) {
throw new IllegalStateException("JdiDebugger.launch() was called but the debugger was already loaded");
}
if (machineLoader != null && !selfRestart) {
return;
}
autoRestart = true;
if (!selfRestart) {
machineLoader = new MachineLoaderThread();
}
selfRestart = false;
machineLoader.setPriority(loaderPriority);
machineLoader.start();
}
| Close this VM, possibly restart it.
|
@Override
public synchronized void close(boolean restart)
{
if (vmRef != null) {
autoRestart = restart;
selfRestart = restart;
if (selfRestart) {
machineLoader = new MachineLoaderThread();
}
vmRef.close();
}
else if (!restart) {
autoRestart = false;
selfRestart = false;
machineLoader = null;
}
}
| Add a listener for DebuggerEvents
|
| @param l
| the DebuggerListener to add
|
public int addDebuggerListener(DebuggerListener l)
{
synchronized (listenerList) {
listenerList.add(l);
return machineState;
}
}
| Remove a listener for DebuggerEvents.
|
| @param l
| the DebuggerListener to remove
|
@OnThread(Tag.Any)
public void removeDebuggerListener(DebuggerListener l)
{
synchronized (listenerList) {
listenerList.remove(l);
}
}
| Get a copy of the listener list.
|
@OnThread(Tag.Any)
private DebuggerListener [] getListeners()
{
synchronized (listenerList) {
return listenerList.toArray(new DebuggerListener[listenerList.size()]);
}
}
| Guess a suitable name for an object about to be put on the object bench.
|
| @param className
| the fully qualified name of the class of object
| @return a String suitable as a name for an object on the object bench.
|
public String guessNewName(String className)
{
className = className.replace('[', ' ').replace(']', ' ').trim();
String baseName = JavaNames.getBase(className);
int stringEndIndex = baseName.length() > Invoker.OBJ_NAME_LENGTH ? Invoker.OBJ_NAME_LENGTH : baseName.length();
String newName = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1, stringEndIndex);
int num = 1;
synchronized(this) {
while (usedNames.contains(newName + num))
{num++;
}
}
return newName + num;
}
public String guessNewName(DebuggerObject obj)
{
String name = null;
DebuggerClass cls = obj.getClassRef();
if (cls.isEnum()) {
Value val = obj.getObjectReference();
name = JdiUtils.getJdiUtils().getValueString(val);
}
if (name == null) {
name = cls.getName();
}
return guessNewName(name);
}
| Create a class loader in the debugger.
| @param bpClassLoader the class loader that should be used to load the user classes in the remote VM.
|
public void newClassLoader(BPClassLoader bpClassLoader)
{
VMReference vmr = null;
synchronized (JdiDebugger.this)
{
if (bpClassLoader != null)
{
lastProjectClassLoader = bpClassLoader;
}
else
{
return;
}
vmr = getVMNoWait();
if (vmr != null)
{
usedNames.clear();
}
}
if (vmr != null)
{
try
{
vmr.clearAllBreakpoints();
vmr.newClassLoader(bpClassLoader.getURLs());
}
catch (VMDisconnectedException vmde)
{
}
}
}
| Remove all breakpoints in the given class.
|
@OnThread(Tag.Any)
public void removeBreakpointsForClass(String className)
{
VMReference vmr = getVMNoWait();
if (vmr != null) {
vmr.clearBreakpointsForClass(className);
}
}
| Add a debugger object into the project scope.
|
| @param newInstanceName
| the name of the object dob the object itself
| @return true if the object could be added with this name, false if there
| was a name clash.
|
public boolean addObject(String scopeId, String newInstanceName, DebuggerObject dob)
{
VMReference vmr = getVMNoWait();
if (vmr != null) {
vmr.addObject(scopeId, newInstanceName, ((JdiObject) dob).getObjectReference());
synchronized (this) {
usedNames.add(newInstanceName);
}
}
return true;
}
| Remove an object from a package scope (when removed from object bench).
|
public void removeObject(String scopeId, String instanceName)
{
VMReference vmr = getVMNoWait();
if (vmr != null) {
vmr.removeObject(scopeId, instanceName);
}
}
| Return the debugger objects that exist in the debugger.
|
| @return a Map of (String name, DebuggerObject obj) entries
|
public Map getObjects()
{
throw new IllegalStateException("not implemented");
}
| Return the machine status; one of the "machine state" constants:
|* NOTREADY, IDLE, RUNNING, or SUSPENDED.
*/
public int getStatus()
{
return machineState;
}
/*
* @see bluej.debugger.Debugger#getMirror(java.lang.String)
public DebuggerObject getMirror(String value)
{
VMReference vmr = getVM();
if (vmr != null) {
try {
StringReference stringReference;
do
{
stringReference = vmr.getMirror(value);
try
{
stringReference.disableCollection();
}
catch (ObjectCollectedException e)
{
}
}
while (stringReference.isCollected()){;
return JdiObject.getDebuggerObject(stringReference);
}
}
catch (VMDisconnectedException vde) {
}
catch (VMOutOfMemoryException vmoome) {
}
}
return null;
}
| Return the text of the last exception.
|
public ExceptionDescription getException()
{
return lastException;
}
| Run the setUp() method of a test class and return the created objects.
|
| @param className
| the fully qualified name of the class
| @return a Map of (String name, DebuggerObject obj) entries
| null if an error occurs (such as VM termination)
|
@OnThread(Tag.Any)
public FXPlatformSupplier
top,
use,
map,
class JdiDebugger
. JdiDebugger
. setUserLibraries
. launch
. close
. addDebuggerListener
. removeDebuggerListener
. getListeners
. guessNewName
. guessNewName
. newClassLoader
. removeBreakpointsForClass
. addObject
. removeObject
. getObjects
. getMirror
. getException
. runTestSetUp
. runTestMethod
. disposeWindows
. instantiateClass
. instantiateClass
. getClass
. fireTargetEvent
. raiseStateChangeEvent
. doStateChange
. toggleBreakpoint
. toggleBreakpoint
. toggleBreakpoint
. breakpoint
. screenBreakpoint
. vmDisconnect
. threadStart
. threadDeath
. getVM
. getVMNoWait
top,
use,
map,
class MachineLoaderThread
. run
. getVM
. getVMNoWait
. emitThreadHaltEvent
. emitThreadResumedEvent
. threadHalted
. threadResumed
. serverThreadResumed
. setRunOnThread
1339 neLoCode
+ 165 LoComm