0001: /*BEGIN_COPYRIGHT_BLOCK
0002: *
0003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
0004: * All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions are met:
0008: * * Redistributions of source code must retain the above copyright
0009: * notice, this list of conditions and the following disclaimer.
0010: * * Redistributions in binary form must reproduce the above copyright
0011: * notice, this list of conditions and the following disclaimer in the
0012: * documentation and/or other materials provided with the distribution.
0013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
0014: * names of its contributors may be used to endorse or promote products
0015: * derived from this software without specific prior written permission.
0016: *
0017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
0018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
0019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
0020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
0021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
0026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0028: *
0029: * This software is Open Source Initiative approved Open Source Software.
0030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
0031: *
0032: * This file is part of DrJava. Download the current version of this project
0033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
0034: *
0035: * END_COPYRIGHT_BLOCK*/
0036:
0037: package edu.rice.cs.drjava.model.debug.jpda;
0038:
0039: import java.io.*;
0040: import java.util.Enumeration;
0041: import java.util.Iterator;
0042: import java.util.LinkedList;
0043: import java.util.List;
0044: import java.util.Map;
0045: import java.util.NoSuchElementException;
0046: import java.util.Stack;
0047: import java.util.StringTokenizer;
0048: import java.util.Vector;
0049:
0050: // DrJava stuff
0051: import edu.rice.cs.util.StringOps;
0052: import edu.rice.cs.util.UnexpectedException;
0053: import edu.rice.cs.util.swing.Utilities;
0054: import edu.rice.cs.drjava.model.GlobalModel;
0055: import edu.rice.cs.drjava.model.repl.DefaultInteractionsModel;
0056: import edu.rice.cs.drjava.model.repl.DummyInteractionsListener;
0057: import edu.rice.cs.drjava.model.repl.InteractionsListener;
0058: import edu.rice.cs.drjava.model.GlobalModelListener;
0059: import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
0060: import edu.rice.cs.util.Log;
0061: import edu.rice.cs.util.Lambda;
0062: import edu.rice.cs.drjava.model.debug.*;
0063:
0064: import com.sun.jdi.*;
0065: import com.sun.jdi.connect.*;
0066: import com.sun.jdi.request.*;
0067: import com.sun.jdi.event.*;
0068: import javax.swing.SwingUtilities;
0069:
0070: /** An integrated debugger which attaches to the Interactions JVM using
0071: * Sun's Java Platform Debugger Architecture (JPDA/JDI) interface.
0072: *
0073: * Every public method in this class throws an llegalStateException if
0074: * it is called while the debugger is not active, except for isAvailable,
0075: * isReady, and startUp. Public methods also throw a DebugException if
0076: * the EventHandlerThread has caught an exception.
0077: *
0078: * @version $Id: JPDADebugger.java 4255 2007-08-28 19:17:37Z mgricken $
0079: */
0080: public class JPDADebugger implements Debugger {
0081:
0082: /** A log for recording messages in a file. */
0083: private static final Log _log = new Log("GlobalModelTest.txt",
0084: false);
0085:
0086: // private static final boolean printMessages = false;
0087: // private final PrintStream printStream = System.out;
0088: private static final int OBJECT_COLLECTED_TRIES = 5;
0089:
0090: /** Reference to DrJava's model. */
0091: private volatile GlobalModel _model;
0092:
0093: /** VirtualMachine of the interactions JVM. */
0094: private volatile VirtualMachine _vm;
0095:
0096: /** Manages all event requests in JDI. */
0097: private volatile EventRequestManager _eventManager;
0098:
0099: /** Vector of all current Watches. */
0100: private final Vector<DebugWatchData> _watches = new Vector<DebugWatchData>();
0101:
0102: /** Keeps track of any DebugActions whose classes have not yet been loaded, so that EventRequests can be created when the correct
0103: * ClassPrepareEvent occurs.
0104: */
0105: private final PendingRequestManager _pendingRequestManager = new PendingRequestManager(
0106: this );
0107:
0108: /** Provides a way for the JPDADebugger to communicate with the view. */
0109: final DebugEventNotifier _notifier = new DebugEventNotifier();
0110:
0111: /** The running ThreadReference that we are debugging. */
0112: private volatile ThreadReference _runningThread;
0113:
0114: /** Storage for all the threads suspended by this debugger. The "current" thread is the top one on the stack. */
0115: private volatile RandomAccessStack _suspendedThreads;
0116:
0117: /** A handle to the interpreterJVM that we need so we can populate the environment. */
0118: private volatile ObjectReference _interpreterJVM;
0119:
0120: private volatile InteractionsListener _watchListener;
0121:
0122: /** If not null, this field holds an error caught by the EventHandlerThread. */
0123: private volatile Throwable _eventHandlerError;
0124:
0125: /** Builds a new JPDADebugger to debug code in the Interactions JVM, using the JPDA/JDI interfaces.
0126: * Does not actually connect to the interpreterJVM until startUp().
0127: */
0128: public JPDADebugger(GlobalModel model) {
0129: _model = model;
0130: _vm = null;
0131: _eventManager = null;
0132:
0133: _suspendedThreads = new RandomAccessStack();
0134: _runningThread = null;
0135: _interpreterJVM = null;
0136: _eventHandlerError = null;
0137:
0138: // TO DO: Replace this with an InteractionsListener,
0139: // since we really can't talk about SingleDisplayModel here!
0140: _watchListener = new DummyInteractionsListener() {
0141: public void interactionEnded() {
0142: try {
0143: _updateWatches();
0144: } catch (DebugException de) {
0145: _log("couldn't update watches", de);
0146: }
0147: }
0148: };
0149: }
0150:
0151: /** Adds a listener to this JPDADebugger.
0152: * @param listener a listener that reacts on events generated by the JPDADebugger
0153: */
0154: public void addListener(DebugListener listener) {
0155: _notifier.addListener(listener);
0156: _model.getBreakpointManager().addListener(listener);
0157: }
0158:
0159: /** Removes a listener to this JPDADebugger.
0160: * @param listener listener to remove
0161: */
0162: public void removeListener(DebugListener listener) {
0163: _notifier.removeListener(listener);
0164: _model.getBreakpointManager().removeListener(listener);
0165: }
0166:
0167: /** Accessor for the _vm field. Called from DocumentDebugAction and this. */
0168: VirtualMachine getVM() {
0169: return _vm;
0170: }
0171:
0172: /** Logs any unexpected behavior that occurs (but which should not cause DrJava to abort).
0173: * @param message message to print to the log
0174: */
0175: private void _log(String message) {
0176: _log.log(message);
0177: }
0178:
0179: /** Logs any unexpected behavior that occurs (but which should not cause DrJava to abort).
0180: * @param message message to print to the log
0181: * @param t Exception or Error being logged
0182: */
0183: private void _log(String message, Throwable t) {
0184: _log.log(message, t);
0185: }
0186:
0187: /** Returns whether the debugger is available in this copy of DrJava. This method does not indicate whether the
0188: * debugger is ready to be used, which is indicated by isReady().
0189: */
0190: public boolean isAvailable() {
0191: return true;
0192: }
0193:
0194: public DebugModelCallback callback() {
0195: return new DebugModelCallback() {
0196: };
0197: }
0198:
0199: /** Returns whether the debugger is currently enabled. */
0200: public boolean isReady() {
0201: return _vm != null;
0202: }
0203:
0204: /** Ensures that debugger is active. Should be called by every public method in the debugger except for startUp().
0205: * Assumes lock is already held.
0206: * @throws IllegalStateException if debugger is not active
0207: * @throws DebugException if an exception was detected in the EventHandlerThread
0208: */
0209: private void _ensureReady() throws DebugException {
0210: if (!isReady())
0211: throw new IllegalStateException("Debugger is not active.");
0212:
0213: if (_eventHandlerError != null) {
0214: Throwable t = _eventHandlerError;
0215: _eventHandlerError = null;
0216: throw new DebugException(
0217: "Error in Debugger Event Handler: " + t);
0218: }
0219: }
0220:
0221: /** Records that an error occurred in the EventHandlerThread. The next call to _ensureReady() will fail, indicating
0222: * that the error occurred. Not private because EventHandlerThread accesses it.
0223: * @param t Error occurring in the EventHandlerThread
0224: */
0225: void eventHandlerError(Throwable t) {
0226: _log("Error in EventHandlerThread: " + t);
0227: _eventHandlerError = t;
0228: }
0229:
0230: /** Attaches the debugger to the Interactions JVM to prepare for debugging. */
0231: public synchronized void startUp() throws DebugException {
0232: if (!isReady()) {
0233: _eventHandlerError = null;
0234: // check if all open documents are in sync
0235: for (OpenDefinitionsDocument doc : _model
0236: .getOpenDefinitionsDocuments()) {
0237: doc.checkIfClassFileInSync();
0238: }
0239:
0240: try {
0241: _attachToVM();
0242: } catch (DebugException e1) { // We sometimes see ConnectExceptions stating that the connection was refused
0243: try {
0244: try {
0245: Thread.sleep(100);
0246: } // Give any temporary connection problems a chance to resolve
0247: catch (InterruptedException e) { /* ignore */
0248: }
0249: _attachToVM();
0250: System.out
0251: .println("Two attempts required for debugger to attach to slave JVM");
0252: } catch (DebugException e2) {
0253: try {
0254: Thread.sleep(100);
0255: } // Give any temporary connection problems a chance to resolve
0256: catch (InterruptedException e) { /* ignore */
0257: }
0258: _attachToVM();
0259: System.out
0260: .println("Three attempts required for debugger to attach to slave JVM");
0261: } // if we throw another exception, three strikes and we're out
0262: }
0263:
0264: // Listen for events when threads die
0265: ThreadDeathRequest tdr = _eventManager
0266: .createThreadDeathRequest();
0267: tdr.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
0268: tdr.enable();
0269:
0270: // Listen for events from JPDA in a new thread
0271: EventHandlerThread eventHandler = new EventHandlerThread(
0272: this , _vm);
0273: eventHandler.start();
0274:
0275: /* Move the following command to the end of the event queue so that it is done outside outside the readLock
0276: * held when this method is called from an InteractionsListener. */
0277: SwingUtilities.invokeLater(new Runnable() {
0278: public void run() {
0279: _model.getInteractionsModel().addListener(
0280: _watchListener);
0281: }
0282: });
0283:
0284: // re-set breakpoints that have already been set
0285: Vector<Breakpoint> oldBreakpoints = new Vector<Breakpoint>(
0286: _model.getBreakpointManager().getRegions());
0287: _model.getBreakpointManager().clearRegions();
0288: for (int i = 0; i < oldBreakpoints.size(); i++) {
0289: Breakpoint bp = oldBreakpoints.get(i);
0290: setBreakpoint(new JPDABreakpoint(bp.getDocument(), bp
0291: .getOffset(), bp.getLineNumber(), bp
0292: .isEnabled(), this ));
0293: }
0294: }
0295:
0296: else
0297: // Already started
0298: throw new IllegalStateException(
0299: "Debugger has already been started.");
0300: }
0301:
0302: /** Handles the details of attaching to the interpreterJVM. Assume lock is already held. */
0303: private void _attachToVM() throws DebugException {
0304: // Blocks until the interpreter has registered if hasn't already. Blocks all synchronized methods in this class.
0305: _model.waitForInterpreter();
0306:
0307: // Get the connector
0308: AttachingConnector connector = _getAttachingConnector();
0309:
0310: // Try to connect on our debug port
0311: Map<String, Connector.Argument> args = connector
0312: .defaultArguments();
0313: Connector.Argument port = args.get("port");
0314: Connector.Argument host = args.get("hostname");
0315: try {
0316: int debugPort = _model.getDebugPort();
0317: port.setValue("" + debugPort);
0318: host.setValue("127.0.0.1"); // necessary if hostname can't be resolved
0319: _vm = connector.attach(args);
0320: _eventManager = _vm.eventRequestManager();
0321: } catch (Exception e) {
0322: throw new DebugException("Could not connect to VM: " + e);
0323: }
0324:
0325: _interpreterJVM = _getInterpreterJVMRef();
0326: }
0327:
0328: /** Returns an attaching connector to use for connecting to the interpreter JVM. Assumes lock is already held. */
0329: private AttachingConnector _getAttachingConnector()
0330: throws DebugException {
0331: VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
0332: List<AttachingConnector> connectors = vmm.attachingConnectors();
0333: AttachingConnector connector = null;
0334: for (AttachingConnector conn : connectors) {
0335: if (conn.name().equals("com.sun.jdi.SocketAttach"))
0336: connector = conn;
0337: }
0338: if (connector == null)
0339: throw new DebugException(
0340: "Could not find an AttachingConnector!");
0341: return connector;
0342: }
0343:
0344: /** Returns an ObjectReference to the singleton instance of the InterpreterJVM class in the virtual machine being
0345: * debugged. This is used to mainupulate interpreters at breakpoints. Assumes lock is already held.
0346: */
0347: private ObjectReference _getInterpreterJVMRef()
0348: throws DebugException {
0349: String className = "edu.rice.cs.drjava.model.repl.newjvm.InterpreterJVM";
0350: List<ReferenceType> referenceTypes = _vm
0351: .classesByName(className); // Added parameterization <ReferenceType>. JDK 1.5 will eliminate this warning
0352: if (referenceTypes.size() > 0) {
0353: ReferenceType rt = referenceTypes.get(0);
0354: Field field = rt.fieldByName("ONLY");
0355: if (field == null)
0356: throw new DebugException("Unable to get ONLY field");
0357: return (ObjectReference) rt.getValue(field);
0358: } else
0359: throw new DebugException(
0360: "Could not get a reference to interpreterJVM");
0361: }
0362:
0363: /** Disconnects the debugger from the Interactions JVM and cleans up any state.
0364: * @throws IllegalStateException if debugger is not ready
0365: */
0366: public synchronized void shutdown() {
0367: if (isReady()) {
0368: Runnable command = new Runnable() {
0369: public void run() {
0370: _model.getInteractionsModel().removeListener(
0371: _watchListener);
0372: }
0373: };
0374:
0375: /* Use SwingUtilities.invokeLater rather than Utilities.invokeLater because we want to defer executing this
0376: * code after pending events (that may involve the _watchListener) */
0377: SwingUtilities.invokeLater(command);
0378:
0379: _removeAllDebugInterpreters();
0380:
0381: try {
0382: _vm.dispose();
0383: } catch (VMDisconnectedException vmde) {
0384: //VM was shutdown prematurely
0385: } finally {
0386: _model.getInteractionsModel().setToDefaultInterpreter();
0387: _vm = null;
0388: _suspendedThreads = new RandomAccessStack();
0389: _eventManager = null;
0390: _runningThread = null;
0391: try {
0392: _updateWatches();
0393: } catch (DebugException de) {
0394: // Couldn't remove breakpoints/watches
0395: _log("Could not remove breakpoints/watches or update watches: "
0396: + de);
0397: }
0398: }
0399: }
0400: }
0401:
0402: /** Returns the current EventRequestManager from JDI, or null if startUp() has not been called. */
0403: EventRequestManager getEventRequestManager() {
0404: return _eventManager;
0405: }
0406:
0407: /** Returns the pending request manager used by the debugger. */
0408: PendingRequestManager getPendingRequestManager() {
0409: return _pendingRequestManager;
0410: }
0411:
0412: /**
0413: * Sets the debugger's currently active thread.
0414: * This method assumes that the given thread is already suspended.
0415: * Returns true if this actually changed the suspended thread
0416: * by pushing it onto the stack of suspended threads. Returns
0417: * false if this thread was already selected.
0418: *
0419: * The return value fixes a bug that occurs if the user steps
0420: * into a breakpoint.
0421: *
0422: * @throws IllegalArgumentException if thread is not suspended.
0423: */
0424: synchronized boolean setCurrentThread(ThreadReference thread) {
0425: if (!thread.isSuspended()) {
0426: throw new IllegalArgumentException(
0427: "Thread must be suspended to set as current. Given: "
0428: + thread);
0429: }
0430:
0431: try {
0432: if ((_suspendedThreads.isEmpty() || !_suspendedThreads
0433: .contains(thread.uniqueID()))
0434: && (thread.frameCount() > 0)) {
0435: _suspendedThreads.push(thread);
0436: return true;
0437: } else
0438: return false;
0439: } catch (IncompatibleThreadStateException itse) {
0440: // requesting stack frames should be fine, since the thread must be
0441: // suspended or frameCount() is not called
0442: throw new UnexpectedException(itse);
0443: }
0444: }
0445:
0446: /** Sets the notion of current thread to the one contained in threadData. The thread must be suspended. (Note: the
0447: * intention is for this method to suspend the thread if necessary, but this is not yet implemented. The catch is
0448: * that any manually suspended threads won't cooperate with the debug interpreters; the thread must be suspended by
0449: * a breakpoint or step.)
0450: * @param threadData Thread to set as current
0451: * @throws IllegalStateException if debugger is not ready
0452: * @throws IllegalArgumentException if threadData is null or not suspended
0453: */
0454: public synchronized void setCurrentThread(DebugThreadData threadData)
0455: throws DebugException {
0456: _ensureReady();
0457:
0458: if (threadData == null) {
0459: throw new IllegalArgumentException(
0460: "Cannot set current thread to null.");
0461: }
0462:
0463: ThreadReference threadRef = _getThreadFromDebugThreadData(threadData);
0464:
0465: // Special case to avoid overhead of scrollToSource() if we
0466: // are selecting the thread we have already selected currently
0467:
0468: // Currently disabled, so we will always scroll to source, even if the
0469: // thread is already selected.
0470: // if ( _suspendedThreads.size() > 0 &&
0471: // _suspendedThreads.peek().uniqueID() == threadRef.uniqueID() ) {
0472: // return;
0473: // }
0474:
0475: // if we switch to a currently suspended thread, we need to remove
0476: // it from the stack and put it on the top
0477: if (_suspendedThreads.contains(threadRef.uniqueID()))
0478: _suspendedThreads.remove(threadRef.uniqueID());
0479:
0480: if (!threadRef.isSuspended()) {
0481: throw new IllegalArgumentException(
0482: "Given thread must be suspended.");
0483: // threadRef.suspend();
0484: //
0485: // try{
0486: // if ( threadRef.frameCount() <= 0 ) {
0487: // printMessage(threadRef.name() + " could not be suspended. It had no stackframes.");
0488: // _suspendedThreads.push(threadRef);
0489: // resume();
0490: // return;
0491: // }
0492: // }
0493: // catch(IncompatibleThreadStateException ex) {
0494: // throw new UnexpectedException(ex);
0495: // }
0496: //
0497: // //
0498: // // Step now so that we can get an interpreter,
0499: // // do not notify (hence the false argument)
0500: // _stepHelper(StepRequest.STEP_OVER, false);
0501: //return;
0502: }
0503:
0504: _suspendedThreads.push(threadRef);
0505:
0506: try {
0507: if (threadRef.frameCount() <= 0) {
0508: printMessage(threadRef.name()
0509: + " could not be suspended since it has no stackframes.");
0510: resume();
0511: return;
0512: }
0513: } catch (IncompatibleThreadStateException e) {
0514: throw new DebugException("Could not suspend thread: " + e);
0515: }
0516:
0517: // Activate the debug interpreter for interacting with this thread
0518: _switchToInterpreterForThreadReference(threadRef);
0519: _switchToSuspendedThread();
0520: printMessage("The current thread has changed.");
0521: }
0522:
0523: /** Returns the currently selected thread for the debugger. */
0524: ThreadReference getCurrentThread() {
0525: // Current thread is the top one on the stack
0526: return _suspendedThreads.peek();
0527: }
0528:
0529: /** Returns the suspended thread at the current index of the stack.
0530: * @param i index into the stack of suspended threads
0531: */
0532: ThreadReference getThreadAt(int i) {
0533: return _suspendedThreads.peekAt(i);
0534: }
0535:
0536: /** Returns the running thread currently tracked by the debugger. */
0537: ThreadReference getCurrentRunningThread() {
0538: return _runningThread;
0539: }
0540:
0541: /** Returns whether the debugger currently has any suspended threads. */
0542: public synchronized boolean hasSuspendedThreads()
0543: throws DebugException {
0544: if (!isReady())
0545: return false;
0546: return _suspendedThreads.size() > 0;
0547: }
0548:
0549: /** Returns whether the debugger's current thread is suspended. */
0550: public synchronized boolean isCurrentThreadSuspended()
0551: throws DebugException {
0552: if (!isReady())
0553: return false;
0554: return hasSuspendedThreads() && !hasRunningThread();
0555: }
0556:
0557: /** Returns whether the thread the debugger is tracking is now running. */
0558: public synchronized boolean hasRunningThread()
0559: throws DebugException {
0560: if (!isReady())
0561: return false;
0562: return _runningThread != null;
0563: }
0564:
0565: /** Returns a Vector with all the loaded ReferenceTypes for the given class name (empty if the class could not be
0566: * found). Makes no attempt to load the class if it is not already loaded.
0567: * <p>
0568: * If custom class loaders are in use, multiple copies of the class may be loaded, so all are returned.
0569: */
0570: Vector<ReferenceType> getReferenceTypes(String className) {
0571: return getReferenceTypes(className, DebugAction.ANY_LINE);
0572: }
0573:
0574: /** Returns a Vector with the loaded ReferenceTypes for the given class name
0575: * (empty if the class could not be found). Makes no attempt to load the
0576: * class if it is not already loaded. If the lineNumber is not
0577: * DebugAction.ANY_LINE, this method ensures that the returned ReferenceTypes
0578: * contain the given lineNumber, searching through inner classes if necessary.
0579: * If no inner classes contain the line number, an empty Vector is returned.
0580: * <p>
0581: * If custom class loaders are in use, multiple copies of the class
0582: * may be loaded, so all are returned.
0583: */
0584: synchronized Vector<ReferenceType> getReferenceTypes(
0585: String className, int lineNumber) {
0586: // Get all classes that match this name
0587: List<ReferenceType> classes;
0588:
0589: try {
0590: classes = _vm.classesByName(className);
0591: } catch (VMDisconnectedException vmde) {
0592: // We're quitting, return empty Vector.
0593: return new Vector<ReferenceType>();
0594: }
0595:
0596: // Return each valid reference type
0597: Vector<ReferenceType> refTypes = new Vector<ReferenceType>();
0598: ReferenceType ref;
0599: for (int i = 0; i < classes.size(); i++) {
0600: ref = classes.get(i);
0601:
0602: if (lineNumber != DebugAction.ANY_LINE) {
0603: List<Location> lines = new LinkedList<Location>();
0604: try {
0605: lines = ref.locationsOfLine(lineNumber); // JDK 1.5 will eliminate this type warning
0606: } catch (AbsentInformationException aie) {
0607: // try looking in inner classes
0608: } catch (ClassNotPreparedException cnpe) {
0609: // try the next class, maybe loaded by a different classloader
0610: continue;
0611: }
0612: // If lines.size > 0, lineNumber was found in ref
0613: if (lines.size() == 0) {
0614: // The ReferenceType might be in an inner class, so
0615: // look for locationsOfLine for nestedTypes
0616: List<ReferenceType> innerRefs = ref.nestedTypes(); // Added parameterization <ReferenceType>. JDK 1.5 will eliminate this type warning
0617: ref = null;
0618: for (int j = 0; j < innerRefs.size(); j++) {
0619: try {
0620: ReferenceType currRef = innerRefs.get(j);
0621: lines = currRef.locationsOfLine(lineNumber); // JDK 1.5 will eliminate this type warning
0622: if (lines.size() > 0) {
0623: ref = currRef;
0624: break;
0625: }
0626: } catch (AbsentInformationException aie) {
0627: // skipping this inner class, look in another
0628: } catch (ClassNotPreparedException cnpe) {
0629: // skipping this inner class, look in another
0630: }
0631: }
0632: }
0633: }
0634: if ((ref != null) && ref.isPrepared()) {
0635: refTypes.add(ref);
0636: }
0637: }
0638: return refTypes;
0639: }
0640:
0641: /** Assumes lock is already held.
0642: * @return The thread in the virtual machine with name d.uniqueID()
0643: * @throws NoSuchElementException if the thread could not be found
0644: */
0645: private ThreadReference _getThreadFromDebugThreadData(
0646: DebugThreadData d) throws NoSuchElementException {
0647: List<ThreadReference> threads = _vm.allThreads();
0648: Iterator<ThreadReference> iterator = threads.iterator();
0649: while (iterator.hasNext()) {
0650: ThreadReference threadRef = iterator.next();
0651: if (threadRef.uniqueID() == d.getUniqueID()) {
0652: return threadRef;
0653: }
0654: }
0655: // Thread not found
0656: throw new NoSuchElementException("Thread " + d.getName()
0657: + " not found in virtual machine!");
0658: }
0659:
0660: /**
0661: * Suspends all the currently running threads in the virtual machine.
0662: *
0663: * Not currently in use/available, since it is incompatible with
0664: * the debug interpreters.
0665: *
0666: public synchronized void suspendAll() {
0667: _ensureReady();
0668: List threads = _vm.allThreads();
0669: Iterator iterator = threads.iterator();
0670: ThreadReference threadRef = null;
0671:
0672: while(iterator.hasNext()) {
0673: threadRef = (ThreadReference)iterator.next();
0674:
0675: if ( !threadRef.isSuspended() ) {
0676: threadRef.suspend();
0677: _suspendedThreads.push(threadRef);
0678: }
0679: }
0680: _runningThread = null;
0681: }*/
0682:
0683: /**
0684: * Suspends execution of the thread referenced by threadData.
0685: *
0686: * Not in use/available, since it is currently incompatible with the
0687: * debug interpreters. (Can't execute code in a suspended thread unless
0688: * it was suspended with a breakpoint/step.)
0689: *
0690: public synchronized void suspend(DebugThreadData threadData)
0691: throws DebugException
0692: {
0693: _ensureReady();
0694: // setCurrentThread suspends if necessary
0695: setCurrentThread(threadData);
0696: _runningThread = null;
0697: }*/
0698:
0699: /** Resumes the thread currently being debugged without removing the debug interpreter or switching to the next
0700: * suspended thread. Assumes lock is already held.
0701: */
0702: private void _resumeFromStep() throws DebugException {
0703: _resumeHelper(true);
0704: }
0705:
0706: /** Resumes the thread currently being debugged, copying back all variables from the current debug interpreter. */
0707: public synchronized void resume() throws DebugException {
0708: _ensureReady();
0709: _resumeHelper(false);
0710: }
0711:
0712: /** Resumes execution of the currently suspended thread. Assumes lock is already held.
0713: * @param fromStep Whether to copy back the variables from the current debug interpreter and switch to the next
0714: * suspended thread.
0715: */
0716: private void _resumeHelper(boolean fromStep) throws DebugException {
0717: try {
0718: ThreadReference thread = _suspendedThreads.pop();
0719:
0720: _log.log("In resumeThread()");
0721: _resumeThread(thread, fromStep);
0722: } catch (NoSuchElementException e) {
0723: throw new DebugException("No thread to resume.");
0724: }
0725: }
0726:
0727: /** Resumes the given thread, copying back any variables from its associated debug interpreter.
0728: * @param threadData Thread to resume
0729: */
0730: public synchronized void resume(DebugThreadData threadData)
0731: throws DebugException {
0732: _ensureReady();
0733: ThreadReference thread = _suspendedThreads.remove(threadData
0734: .getUniqueID());
0735: _resumeThread(thread, false);
0736: }
0737:
0738: /** Resumes the given thread, only copying variables from its debug interpreter if shouldCopyBack is true. Assumes
0739: * lock on this is already held.
0740: * @param thread Thread to resume
0741: * @param fromStep Whether to copy back the variables from
0742: * the current debug interpreter and switch to the next
0743: * suspended thread.
0744: * @throws IllegalArgumentException if thread is null
0745: */
0746: private void _resumeThread(ThreadReference thread, boolean fromStep)
0747: throws DebugException {
0748: if (thread == null) {
0749: throw new IllegalArgumentException(
0750: "Cannot resume a null thread");
0751: }
0752:
0753: int suspendCount = thread.suspendCount();
0754: _log.log("Getting suspendCount = " + suspendCount);
0755:
0756: _runningThread = thread;
0757: if (!fromStep) {
0758: // Copy variables back into the thread
0759: _copyVariablesFromInterpreter();
0760: _updateWatches();
0761: }
0762: try {
0763: _removeCurrentDebugInterpreter(fromStep);
0764: currThreadResumed();
0765: } catch (DebugException e) {
0766: throw new UnexpectedException(e);
0767: }
0768:
0769: // Must resume the correct number of times
0770: for (int i = suspendCount; i > 0; i--)
0771: thread.resume();
0772:
0773: // Notify listeners of a resume
0774:
0775: // Switch to next suspended thread, if any
0776: if (!fromStep && !_suspendedThreads.isEmpty())
0777: _switchToSuspendedThread();
0778: }
0779:
0780: /** Steps the execution of the currently loaded document. */
0781: public synchronized void step(StepType type) throws DebugException {
0782: _ensureReady();
0783: _stepHelper(type, true);
0784: }
0785:
0786: /** Performs a step in the currently suspended thread, only generating a step event if shouldNotify if true. Assumes
0787: * that lock is already held.
0788: * @param type The type of step to perform
0789: * @param shouldNotify Whether to generate a step event
0790: */
0791: private void _stepHelper(StepType type, boolean shouldNotify)
0792: throws DebugException {
0793: if (_suspendedThreads.size() <= 0 || _runningThread != null) {
0794: throw new IllegalStateException(
0795: "Cannot step if the current thread is not suspended.");
0796: }
0797:
0798: _log.log(this + "is About to peek ...");
0799:
0800: ThreadReference thread = _suspendedThreads.peek();
0801: _log.log(this + "is Stepping " + thread.toString());
0802:
0803: // Copy the variables back into the thread from the appropriate interpreter.
0804: // We do this before stepping since DrJava will hang if you try to copy back
0805: // variables after creating the step request.
0806: _runningThread = thread;
0807: _copyVariablesFromInterpreter();
0808:
0809: _log.log(this + " is Deleting pending requests ...");
0810:
0811: // If there's already a step request for the current thread, delete
0812: // it first
0813: List<StepRequest> steps = _eventManager.stepRequests();
0814: for (int i = 0; i < steps.size(); i++) {
0815: StepRequest step = steps.get(i);
0816: if (step.thread().equals(thread)) {
0817: _eventManager.deleteEventRequest(step);
0818: break;
0819: }
0820: }
0821:
0822: _log.log(this + " Issued step request");
0823: int stepFlag = Integer.MIN_VALUE; // should always be changed, but compiler doesn't check that
0824: switch (type) {
0825: case STEP_INTO:
0826: stepFlag = StepRequest.STEP_INTO;
0827: break;
0828: case STEP_OVER:
0829: stepFlag = StepRequest.STEP_OVER;
0830: break;
0831: case STEP_OUT:
0832: stepFlag = StepRequest.STEP_OUT;
0833: break;
0834: }
0835: new Step(this , StepRequest.STEP_LINE, stepFlag);
0836: if (shouldNotify)
0837: notifyStepRequested();
0838: _log.log(this + " About to resume");
0839: _resumeFromStep();
0840: }
0841:
0842: /** Adds a watch on the given field or variable.
0843: * @param field the name of the field we will watch
0844: */
0845: public synchronized void addWatch(String field)
0846: throws DebugException {
0847: // _ensureReady();
0848:
0849: final DebugWatchData w = new DebugWatchData(field);
0850: _watches.add(w);
0851: _updateWatches();
0852:
0853: Utilities.invokeLater(new Runnable() {
0854: public void run() {
0855: _notifier.watchSet(w);
0856: }
0857: });
0858: }
0859:
0860: /**
0861: * Removes any watches on the given field or variable.
0862: * Has no effect if the given field is not being watched.
0863: * @param field the name of the field we will watch
0864: */
0865: public synchronized void removeWatch(String field)
0866: throws DebugException {
0867: // _ensureReady();
0868:
0869: for (int i = 0; i < _watches.size(); i++) {
0870: final DebugWatchData watch = _watches.get(i);
0871: if (watch.getName().equals(field)) {
0872: _watches.remove(i);
0873: Utilities.invokeLater(new Runnable() {
0874: public void run() {
0875: _notifier.watchRemoved(watch);
0876: }
0877: });
0878: }
0879: }
0880: }
0881:
0882: /**
0883: * Removes the watch at the given index.
0884: * @param index Index of the watch to remove
0885: */
0886: public synchronized void removeWatch(int index)
0887: throws DebugException {
0888: // _ensureReady();
0889:
0890: if (index < _watches.size()) {
0891: final DebugWatchData watch = _watches.get(index);
0892: _watches.remove(index);
0893: Utilities.invokeLater(new Runnable() {
0894: public void run() {
0895: _notifier.watchRemoved(watch);
0896: }
0897: });
0898: }
0899: }
0900:
0901: /**
0902: * Removes all watches on existing fields and variables.
0903: */
0904: public synchronized void removeAllWatches() throws DebugException {
0905: // _ensureReady();
0906:
0907: while (_watches.size() > 0) {
0908: removeWatch(_watches.get(0).getName());
0909: }
0910: }
0911:
0912: /**
0913: * Enable or disable the specified breakpoint.
0914: * @param breakpoint breakpoint to change
0915: */
0916: public synchronized void notifyBreakpointChange(
0917: Breakpoint breakpoint) {
0918: _model.getBreakpointManager().changeRegion(breakpoint,
0919: new Lambda<Object, Breakpoint>() {
0920: public Object apply(Breakpoint bp) {
0921: // change has already been made, just notify all listeners
0922: return null;
0923: }
0924: });
0925: }
0926:
0927: /** Toggles whether a breakpoint is set at the given line in the given document.
0928: * @param doc Document in which to set or remove the breakpoint
0929: * @param offset Start offset on the line to set the breakpoint
0930: * @param lineNum Line on which to set or remove the breakpoint, >=1
0931: * @param isEnabled {@code true} if this breakpoint should be enabled
0932: */
0933: public synchronized void toggleBreakpoint(
0934: OpenDefinitionsDocument doc, int offset, int lineNum,
0935: boolean isEnabled) throws DebugException {
0936: Breakpoint breakpoint = _model.getBreakpointManager()
0937: .getRegionAt(doc, offset);
0938:
0939: if (breakpoint == null) {
0940: if (doc.getLineStartPos(offset) == doc
0941: .getLineEndPos(offset)) {
0942: Utilities
0943: .show("Cannot set a breakpoint on an empty line.");
0944: } else {
0945: try {
0946: setBreakpoint(new JPDABreakpoint(doc, offset,
0947: lineNum, isEnabled, this ));
0948: } catch (LineNotExecutableException lnee) {
0949: Utilities.show(lnee.getMessage());
0950: }
0951: }
0952: } else
0953: _model.getBreakpointManager().removeRegion(breakpoint);
0954: }
0955:
0956: /** Sets a breakpoint.
0957: * @param breakpoint The new breakpoint to set
0958: */
0959: public synchronized void setBreakpoint(final Breakpoint breakpoint)
0960: throws DebugException {
0961: breakpoint.getDocument().checkIfClassFileInSync();
0962:
0963: _model.getBreakpointManager().addRegion(breakpoint);
0964: }
0965:
0966: /** Removes a breakpoint. Called from toggleBreakpoint -- even with BPs that are not active.
0967: * @param breakpoint The breakpoint to remove.
0968: */
0969: public synchronized void removeBreakpoint(Breakpoint bp)
0970: throws DebugException {
0971: if (!(bp instanceof JPDABreakpoint)) {
0972: throw new IllegalArgumentException("Unsupported breakpoint");
0973: } else {
0974: JPDABreakpoint breakpoint = (JPDABreakpoint) bp;
0975: Vector<BreakpointRequest> requests = breakpoint
0976: .getRequests();
0977: if (requests.size() > 0 && _eventManager != null) {
0978: // Remove all event requests for this breakpoint
0979: try {
0980: for (int i = 0; i < requests.size(); i++) {
0981: _eventManager.deleteEventRequest(requests
0982: .get(i));
0983: }
0984: } catch (VMMismatchException vme) {
0985: // Not associated with this VM; probably from a previous session.
0986: // Ignore and make sure it gets removed from the document.
0987: _log("VMMismatch when removing breakpoint.", vme);
0988: } catch (VMDisconnectedException vmde) {
0989: // The VM has already disconnected for some reason
0990: // Ignore it and make sure the breakpoint gets removed from the document
0991: _log("VMDisconnected when removing breakpoint.",
0992: vmde);
0993: }
0994: }
0995:
0996: // Always remove from pending request, since it's always there
0997: _pendingRequestManager.removePendingRequest(breakpoint);
0998: }
0999: }
1000:
1001: /** Called when a breakpoint is reached. The Breakpoint object itself should be stored in the
1002: * "debugAction" property on the request.
1003: * @param request The BreakPointRequest reached by the debugger
1004: */
1005: synchronized void reachedBreakpoint(BreakpointRequest request) {
1006: // Utilities.showDebug("JPDADebugger.reachedBreakPoint(" + request + ") called");
1007: Object property = request.getProperty("debugAction");
1008: if ((property != null) && (property instanceof JPDABreakpoint)) {
1009: final JPDABreakpoint breakpoint = (JPDABreakpoint) property;
1010: printMessage("Breakpoint hit in class "
1011: + breakpoint.getClassName() + " [line "
1012: + breakpoint.getLineNumber() + "]");
1013:
1014: Utilities.invokeLater(new Runnable() {
1015: public void run() {
1016: _notifier.breakpointReached(breakpoint);
1017: }
1018: });
1019: } else {
1020: // A breakpoint we didn't set??
1021: _log("Reached a breakpoint without a debugAction property: "
1022: + request);
1023: }
1024: }
1025:
1026: /** Returns all currently watched fields and variables. No synchronization required because _watches is final. */
1027: public Vector<DebugWatchData> getWatches() throws DebugException {
1028: //_ensureReady();
1029: return _watches;
1030: }
1031:
1032: /** Returns a list of all threads being tracked by the debugger. Does not return any threads known to be dead. */
1033: public synchronized Vector<DebugThreadData> getCurrentThreadData()
1034: throws DebugException {
1035: if (!isReady()) {
1036: return new Vector<DebugThreadData>();
1037: }
1038: List<ThreadReference> listThreads; // Add parameterization <ThreadReference> to listThreads.
1039: try {
1040: listThreads = _vm.allThreads();
1041: } catch (VMDisconnectedException vmde) {
1042: // We're quitting, just pass back an empty Vector
1043: return new Vector<DebugThreadData>();
1044: }
1045:
1046: // get an iterator from the list returned by _vm.allThreads()
1047: Iterator<ThreadReference> iter = listThreads.iterator(); // Added parameterization <ThreadReference>.
1048: Vector<DebugThreadData> threads = new Vector<DebugThreadData>();
1049: while (iter.hasNext()) {
1050: try {
1051: threads.add(new JPDAThreadData(iter.next()));
1052: } catch (ObjectCollectedException e) {
1053: // this thread just died, we don't want to list it anyway
1054: }
1055: }
1056: return threads;
1057: }
1058:
1059: /**
1060: * Returns a Vector of DebugStackData for the current suspended thread.
1061: * @throws DebugException if the current thread is running or there
1062: * are no suspended threads
1063: * TO DO: Config option for hiding DrJava subset of stack trace
1064: */
1065: public synchronized Vector<DebugStackData> getCurrentStackFrameData()
1066: throws DebugException {
1067: if (!isReady())
1068: return new Vector<DebugStackData>();
1069:
1070: if (_runningThread != null || _suspendedThreads.size() <= 0) {
1071: throw new DebugException(
1072: "No suspended thread to obtain stack frames.");
1073: }
1074:
1075: try {
1076: ThreadReference thread = _suspendedThreads.peek();
1077: Iterator<StackFrame> iter = thread.frames().iterator(); // Added <StackFrame> parameterization; warning will go away in JDK 1.5
1078: Vector<DebugStackData> frames = new Vector<DebugStackData>();
1079: while (iter.hasNext()) {
1080: frames.add(new JPDAStackData(iter.next()));
1081: }
1082: return frames;
1083: } catch (IncompatibleThreadStateException itse) {
1084: _log("Unable to obtain stack frame.", itse);
1085: return new Vector<DebugStackData>();
1086: } catch (VMDisconnectedException vmde) {
1087: _log(
1088: "VMDisconnected when getting the current stack frame data.",
1089: vmde);
1090: return new Vector<DebugStackData>();
1091: } catch (InvalidStackFrameException isfe) {
1092: _log("The stack frame requested is invalid.", isfe);
1093: return new Vector<DebugStackData>();
1094: }
1095: }
1096:
1097: /** Takes the location of event e, opens the document corresponding to its class and centers the definition pane's
1098: * view on the appropriate line number. Assumes lock on this is already held.
1099: * @param e LocatableEvent containing location to display
1100: */
1101: private void scrollToSource(LocatableEvent e) {
1102: Location location = e.location();
1103:
1104: // First see if doc is stored
1105: EventRequest request = e.request();
1106: Object docProp = request.getProperty("document");
1107: if ((docProp != null)
1108: && (docProp instanceof OpenDefinitionsDocument)) {
1109: openAndScroll((OpenDefinitionsDocument) docProp, location,
1110: true);
1111: } else
1112: scrollToSource(location);
1113: }
1114:
1115: /** Scroll to the location specified by location Assumes lock on this is already held */
1116: private void scrollToSource(Location location) {
1117: scrollToSource(location, true);
1118: }
1119:
1120: /** Return the document associated with this location. A document is preloaded when a debugger step is
1121: * made to avoid the deadlock described in [ 1696060 ] Debugger Infinite Loop.
1122: */
1123: public OpenDefinitionsDocument preloadDocument(Location location) {
1124: OpenDefinitionsDocument doc = null;
1125:
1126: // No stored doc, look on the source root set (later, also the sourcepath)
1127: ReferenceType rt = location.declaringType();
1128: String fileName;
1129: try {
1130: fileName = getPackageDir(rt.name()) + rt.sourceName();
1131: } catch (AbsentInformationException aie) {
1132: // Don't know real source name:
1133: // assume source name is same as file name
1134: String className = rt.name();
1135: String ps = System.getProperty("file.separator");
1136: // replace periods with the System's file separator
1137: className = StringOps.replace(className, ".", ps);
1138:
1139: // crop off the $ if there is one and anything after it
1140: int indexOfDollar = className.indexOf('$');
1141: if (indexOfDollar > -1) {
1142: className = className.substring(0, indexOfDollar);
1143: }
1144:
1145: fileName = className + ".java";
1146: }
1147:
1148: // Check source root set (open files)
1149: File f = _model.getSourceFile(fileName);
1150: if (f != null) {
1151: // Get a document for this file, forcing it to open
1152: try {
1153: doc = _model.getDocumentForFile(f);
1154: } catch (IOException ioe) {
1155: // No doc, so don't notify listener
1156: }
1157: }
1158: return doc;
1159: }
1160:
1161: /** Scroll to the location specified by location. Assumes lock on this is already held. */
1162: private void scrollToSource(Location location,
1163: boolean shouldHighlight) {
1164: OpenDefinitionsDocument doc = preloadDocument(location);
1165: openAndScroll(doc, location, shouldHighlight);
1166: }
1167:
1168: /** Scrolls to the source location specified by the the debug stack data.
1169: * @param stackData Stack data containing location to display
1170: * @throws DebugException if current thread is not suspended
1171: */
1172: public synchronized void scrollToSource(DebugStackData stackData)
1173: throws DebugException {
1174: _ensureReady();
1175: if (_runningThread != null) {
1176: throw new DebugException(
1177: "Cannot scroll to source unless thread is suspended.");
1178: }
1179:
1180: ThreadReference threadRef = _suspendedThreads.peek();
1181: Iterator<StackFrame> i;
1182:
1183: try {
1184: if (threadRef.frameCount() <= 0) {
1185: printMessage("Could not scroll to source. The current thread had no stack frames.");
1186: return;
1187: }
1188: i = threadRef.frames().iterator(); // JDK 1.5 will eliminate this warning
1189: } catch (IncompatibleThreadStateException e) {
1190: throw new DebugException("Unable to find stack frames: "
1191: + e);
1192: }
1193:
1194: while (i.hasNext()) {
1195: StackFrame frame = i.next();
1196:
1197: if (frame.location().lineNumber() == stackData.getLine()
1198: && stackData.getMethod().equals(
1199: frame.location().declaringType().name()
1200: + "."
1201: + frame.location().method().name())) {
1202: scrollToSource(frame.location(), false);
1203: }
1204: }
1205: }
1206:
1207: /** Scrolls to the source of the given breakpoint.
1208: * @param bp the breakpoint
1209: */
1210: public synchronized void scrollToSource(Breakpoint bp) {
1211: openAndScroll(bp.getDocument(), bp.getLineNumber(), bp
1212: .getClassName(), false);
1213: }
1214:
1215: /**
1216: * Gets the Breakpoint object at the specified line in the given class.
1217: * If the given data do not correspond to an actual breakpoint, null is returned.
1218: * @param line the line number of the breakpoint
1219: * @param className the name of the class the breakpoint's in
1220: * @return the Breakpoint corresponding to the line and className, or null if
1221: * there is no such breakpoint.
1222: */
1223: public synchronized Breakpoint getBreakpoint(int line,
1224: String className) {
1225: for (int i = 0; i < _model.getBreakpointManager().getRegions()
1226: .size(); i++) {
1227: Breakpoint bp = _model.getBreakpointManager().getRegions()
1228: .get(i);
1229: if ((bp.getLineNumber() == line)
1230: && (bp.getClassName().equals(className))) {
1231: return bp;
1232: }
1233: }
1234: // bp not found in the list of breakpoints
1235: return null;
1236: }
1237:
1238: /** Opens a document and scrolls to the appropriate location. If doc is null, a message is printed indicating the
1239: * source file could not be found. Assumes lock on this is already held.
1240: * @param doc Document to open
1241: * @param location Location to display
1242: */
1243: private void openAndScroll(OpenDefinitionsDocument doc,
1244: Location location, boolean shouldHighlight) {
1245: openAndScroll(doc, location.lineNumber(), location
1246: .declaringType().name(), shouldHighlight);
1247: }
1248:
1249: /** Opens a document and scrolls to the appropriate location. If doc is null, a message is printed indicating the
1250: * source file could not be found. Assumes lock on this is already held.
1251: * @param doc Document to open
1252: * @param line the line number to display
1253: * @param className the name of the appropriate class
1254: */
1255: private void openAndScroll(final OpenDefinitionsDocument doc,
1256: final int line, String className,
1257: final boolean shouldHighlight) {
1258: // Open and scroll if doc was found
1259: if (doc != null) {
1260: doc.checkIfClassFileInSync();
1261: // change UI if in sync in MainFrame listener
1262:
1263: Utilities.invokeLater(new Runnable() {
1264: public void run() {
1265: _notifier.threadLocationUpdated(doc, line,
1266: shouldHighlight);
1267: }
1268: });
1269: } else
1270: printMessage(" (Source for " + className + " not found.)");
1271: }
1272:
1273: /** Returns the relative directory (from the source root) that the source file with this qualifed name will be in,
1274: * given its package. Returns the empty string for classes without packages.
1275: * TO DO: Move this to a static utility class
1276: * @param className The fully qualified class name
1277: */
1278: static String getPackageDir(String className) {
1279: // Only keep up to the last dot
1280: int lastDotIndex = className.lastIndexOf(".");
1281: if (lastDotIndex == -1) {
1282: // No dots, so no package
1283: return "";
1284: } else {
1285: String packageName = className.substring(0, lastDotIndex);
1286: // replace periods with the System's file separator
1287: String ps = System.getProperty("file.separator");
1288: packageName = StringOps.replace(packageName, ".", ps);
1289: return packageName + ps;
1290: }
1291: }
1292:
1293: /** Prints a message in the Interactions Pane. Not synchronized on this on this because no local state is accessed.
1294: * @param message Message to display
1295: */
1296: void printMessage(String message) {
1297: _model.printDebugMessage(message);
1298: }
1299:
1300: /** Returns whether the given className corresponds to a class
1301: * that is anonymous or has an anonymous enclosing class.
1302: * @param rt the ReferenceType to check
1303: * @return whether the class is anonymous
1304: */
1305: private boolean hasAnonymous(ReferenceType rt) {
1306: String className = rt.name();
1307: StringTokenizer st = new StringTokenizer(className, "$");
1308: while (st.hasMoreElements()) {
1309: String currToken = st.nextToken();
1310: try {
1311: Integer anonymousNum = Integer.valueOf(currToken);
1312: return true;
1313: } catch (NumberFormatException nfe) {
1314: // flow through to false if token cannot be parsed into an int
1315: }
1316: }
1317: return false;
1318: }
1319:
1320: private boolean _getWatchFromInterpreter(DebugWatchData currWatch) {
1321: String currName = currWatch.getName();
1322: // get the value and type from the interactions model
1323: String value = _model.getInteractionsModel()
1324: .getVariableToString(currName);
1325: if (value != null) {
1326: String type = _model.getInteractionsModel()
1327: .getVariableClassName(currName);
1328: currWatch.setValue(value);
1329: currWatch.setType(type);
1330: return true;
1331: } else {
1332: return false;
1333: }
1334: }
1335:
1336: /** Hides all of the values of the watches and their types. Called when there is no debug information. Assumes lock
1337: * is already held.
1338: */
1339: private void _hideWatches() {
1340: for (int i = 0; i < _watches.size(); i++) {
1341: DebugWatchData currWatch = _watches.get(i);
1342: currWatch.hideValueAndType();
1343: }
1344: }
1345:
1346: /** Updates the stored value of each watched field and variable. Synchronization is necessary because this method is
1347: * called from unsynchronized listeners. */
1348: private synchronized void _updateWatches() throws DebugException {
1349: if (!isReady())
1350: return;
1351:
1352: if (_suspendedThreads.size() <= 0) {
1353: // Not suspended, get values in interpreter
1354: for (int i = 0; i < _watches.size(); i++) {
1355: DebugWatchData currWatch = _watches.get(i);
1356: if (!_getWatchFromInterpreter(currWatch))
1357: currWatch.hideValueAndType();
1358: }
1359: return;
1360: // for (int i = 0; i < _watches.size(); i++) {
1361: // DebugWatchData currWatch = _watches.get(i);
1362: // currWatch.hideValueAndType();
1363: // }
1364: // return;
1365: }
1366:
1367: try {
1368: StackFrame currFrame;
1369: List<StackFrame> frames;
1370: ThreadReference thread = _suspendedThreads.peek();
1371: if (thread.frameCount() <= 0) {
1372: printMessage("Could not update watch values. The current thread had no stack frames.");
1373: return;
1374: }
1375: frames = thread.frames(); // JDK 1.5 will eliminate this warning
1376: currFrame = frames.get(0);
1377: Location location = currFrame.location();
1378:
1379: ReferenceType rt = location.declaringType();
1380: ObjectReference obj = currFrame.this Object();
1381: // note: obj is null if we're in a static context
1382:
1383: // Get the name to determine how many $'s there are
1384: String rtName = rt.name();
1385: int numDollars = 0;
1386: int dollarIndex = rtName.indexOf("$", 0);
1387: while (dollarIndex != -1) {
1388: numDollars++;
1389: dollarIndex = rtName.indexOf("$", dollarIndex + 1);
1390: }
1391:
1392: for (int i = 0; i < _watches.size(); i++) {
1393: DebugWatchData currWatch = _watches.get(i);
1394: String currName = currWatch.getName();
1395: if (_getWatchFromInterpreter(currWatch)) {
1396: continue;
1397: }
1398: // // check for "this"
1399: // if (currName.equals("this")) {
1400: // if (obj != null) {
1401: // currWatch.setValue(_getValue(obj));
1402: // currWatch.setType(String.valueOf(obj.type()));
1403: // }
1404: // else {
1405: // // "this" is not defined in a static context
1406: // currWatch.setNoValue();
1407: // currWatch.setNoType();
1408: // }
1409: // continue;
1410: // }
1411:
1412: // // Look for a variable with this name
1413: // LocalVariable localVar = null;
1414: // try {
1415: // frames = thread.frames();
1416: // currFrame = (StackFrame) frames.get(0);
1417: // localVar = currFrame.visibleVariableByName(currName);
1418: // }
1419: // catch (AbsentInformationException aie) {
1420: // // Not compiled with debug flag.... ignore
1421: // }
1422: // catch (InvalidStackFrameException isfe) {
1423: // currWatch.setNoValue();
1424: // currWatch.setNoType();
1425: // _log("Failed to get local var from stack frame", isfe);
1426: // continue;
1427: // }
1428: //
1429: // if (localVar != null) {
1430: // // currWatch.setValue(_getValue(currFrame.getValue(localVar)));
1431: // try {
1432: // Value v = _getValueOfLocalVariable(localVar, thread);
1433: // if (v == null) {
1434: // currWatch.setValue(_getValue(null));
1435: // try {
1436: // currWatch.setType(localVar.type().name());
1437: // }
1438: // catch (ClassNotLoadedException cnle) {
1439: // List classes = _vm.classesByName(localVar.typeName());
1440: // if (!classes.isEmpty()) {
1441: // currWatch.setType(((Type)classes.get(0)).name());
1442: // }
1443: // else {
1444: // currWatch.setTypeNotLoaded();
1445: // }
1446: // }
1447: // }
1448: // else {
1449: // currWatch.setValue(_getValue(v));
1450: // currWatch.setType(v.type().name());
1451: // }
1452: // }
1453: // catch (Exception ex) {
1454: // _log("Exception when getting the value of a local variable", ex);
1455: // currWatch.setNoValue();
1456: // currWatch.setNoType();
1457: // }
1458: // }
1459: // if the variable being watched is not a local variable,
1460: // check if it's a field
1461: ReferenceType outerRt = rt;
1462: ObjectReference outer = obj; // (null if static context)
1463: Field field = outerRt.fieldByName(currName);
1464:
1465: if (obj != null) {
1466: // We're not in a static context
1467:
1468: // If we don't find it in this class, loop through any enclosing
1469: // classes. Start at this$N, where N is the number of dollar signs in
1470: // the reference type's name, minus one.
1471: int outerIndex = numDollars - 1;
1472: if (hasAnonymous(outerRt)) {
1473: // We don't know the appropriate this$N to look for so we have to
1474: // search for a field that begins with this$.
1475: List<Field> fields = outerRt.allFields(); // This type warning will go away in JDK 1.5
1476: Iterator<Field> iter = fields.iterator();
1477: while (iter.hasNext()) {
1478: Field f = iter.next();
1479: String name = f.name();
1480: if (name.startsWith("this$")) {
1481: int lastIndex = name.lastIndexOf("$");
1482: outerIndex = Integer.valueOf(
1483: name.substring(lastIndex + 1,
1484: name.length()))
1485: .intValue();
1486: break;
1487: }
1488: }
1489: }
1490: Field outerThis = outerRt.fieldByName("this$"
1491: + outerIndex);
1492: if (field == null) {
1493: // Try concatenating "val$" to the beginning of the field in
1494: // case it's a final local variable of the outer class
1495: field = outerRt.fieldByName("val$" + currName);
1496: }
1497:
1498: while ((field == null) && (outerThis != null)) {
1499: outer = (ObjectReference) outer
1500: .getValue(outerThis);
1501: if (outer == null) {
1502: // We're probably in the constructor and this$N has
1503: // not yet been initialized. We can't do anything, so just
1504: // break display no value.
1505: break;
1506: }
1507: outerRt = outer.referenceType();
1508: field = outerRt.fieldByName(currName);
1509:
1510: if (field == null) {
1511: // Try concatenating "val$" to the beginning of the field in
1512: // case it's a final local variable of the outer class
1513: field = outerRt.fieldByName("val$"
1514: + currName);
1515:
1516: if (field == null) {
1517: // Enter the loop again with the next outer enclosing class
1518: outerIndex--;
1519: outerThis = outerRt.fieldByName("this$"
1520: + outerIndex);
1521: }
1522: }
1523: }
1524: } else {
1525: // We're in a static context
1526:
1527: // If we don't find it in this class, loop through any enclosing
1528: // classes. Do this by loading any outer classes by invoking the
1529: // method on the class loader that loaded this class and passing
1530: // it the class name with the last class removed each time.
1531: String rtClassName = outerRt.name();
1532: int index = rtClassName.lastIndexOf("$");
1533: while ((field == null) && (index != -1)) {
1534: rtClassName = rtClassName.substring(0, index);
1535: List<ReferenceType> l = _vm
1536: .classesByName(rtClassName); // JDK 1.5 will eliminate this warning
1537: if (l.isEmpty()) {
1538: // field is null, we will end up setting
1539: // the value to no value
1540: break;
1541: }
1542: outerRt = l.get(0);
1543: field = outerRt.fieldByName(currName);
1544:
1545: if (field == null) {
1546: // Enter the loop again with the next outer enclosing class
1547: index = rtClassName.lastIndexOf("$");
1548: }
1549: }
1550: }
1551:
1552: // Try to set the value and type of the field.
1553: // If the field is not static and we are in a static context
1554: // (outer==null), we have to setNoValue.
1555: if ((field != null)
1556: && (field.isStatic() || (outer != null))) {
1557: Value v = (field.isStatic()) ? outerRt
1558: .getValue(field) : outer.getValue(field);
1559: currWatch.setValue(_getValue(v));
1560: try {
1561: currWatch.setType(field.type().name());
1562: } catch (ClassNotLoadedException cnle) {
1563: List<ReferenceType> classes = _vm
1564: .classesByName(field.typeName()); // JDK 1.5 will eliminate this warning
1565: if (!classes.isEmpty()) {
1566: currWatch.setType(classes.get(0).name());
1567: } else {
1568: currWatch.setTypeNotLoaded();
1569: }
1570: }
1571: } else {
1572: currWatch.setNoValue();
1573: currWatch.setNoType();
1574: }
1575:
1576: }
1577: } catch (IncompatibleThreadStateException itse) {
1578: _log("Exception updating watches.", itse);
1579: } catch (InvalidStackFrameException isfe) {
1580: _log("Exception updating watches.", isfe);
1581: } catch (VMDisconnectedException vmde) {
1582: _log("Exception updating watches.", vmde);
1583: shutdown();
1584: }
1585: }
1586:
1587: /**
1588: * Returns a string representation of the given Value from JDI.
1589: * @param value the Value of interest
1590: * @return the String representation of the Value
1591: */
1592: private String _getValue(Value value) throws DebugException {
1593: // Most types work as they are; for the rest, for now, only care about getting
1594: // accurate toString for Objects
1595: if (value == null) {
1596: return "null";
1597: }
1598:
1599: if (!(value instanceof ObjectReference)) {
1600: return value.toString();
1601: }
1602: ObjectReference object = (ObjectReference) value;
1603: ReferenceType rt = object.referenceType();
1604: ThreadReference thread = _suspendedThreads.peek();
1605: List<Method> toStrings = rt.methodsByName("toString"); // JDK 1.5 will eliminate this warning
1606: if (toStrings.size() == 0) {
1607: // not sure how an Object can't have a toString method, but it happens
1608: return value.toString();
1609: }
1610: // Assume that there's only one method named toString
1611: Method method = toStrings.get(0);
1612: try {
1613: Value stringValue = object.invokeMethod(thread, method,
1614: new LinkedList<Value>(),
1615: ObjectReference.INVOKE_SINGLE_THREADED);
1616: if (stringValue == null)
1617: return "null";
1618: return stringValue.toString();
1619: } catch (InvalidTypeException ite) {
1620: // shouldn't happen, not passing any arguments to toString()
1621: throw new UnexpectedException(ite);
1622: } catch (ClassNotLoadedException cnle) {
1623: // once again, no arguments
1624: throw new UnexpectedException(cnle);
1625: } catch (IncompatibleThreadStateException itse) {
1626: throw new DebugException(
1627: "Cannot determine value from thread: " + itse);
1628: } catch (InvocationException ie) {
1629: throw new DebugException("Could not invoke toString: " + ie);
1630: }
1631: }
1632:
1633: /** @return the appropriate Method to call in the InterpreterJVM in order to define a variable of the type val. */
1634: private Method _getDefineVariableMethod(
1635: ReferenceType interpreterRef, Value val)
1636: throws DebugException {
1637: List<Method> methods;
1638: String signature_beginning = "(Ljava/lang/String;";
1639: String signature_end = ")V";
1640: String signature_mid;
1641: String signature;
1642:
1643: if ((val == null) || (val instanceof ObjectReference)) {
1644: signature_mid = "Ljava/lang/Object;Ljava/lang/Class;";
1645: } else if (val instanceof BooleanValue) {
1646: signature_mid = "Z";
1647: } else if (val instanceof ByteValue) {
1648: signature_mid = "B";
1649: } else if (val instanceof CharValue) {
1650: signature_mid = "C";
1651: } else if (val instanceof DoubleValue) {
1652: signature_mid = "D";
1653: } else if (val instanceof FloatValue) {
1654: signature_mid = "F";
1655: } else if (val instanceof IntegerValue) {
1656: signature_mid = "I";
1657: } else if (val instanceof LongValue) {
1658: signature_mid = "J";
1659: } else if (val instanceof ShortValue) {
1660: signature_mid = "S";
1661: } else {
1662: throw new IllegalArgumentException(
1663: "Tried to define a variable which is\n"
1664: + "not an Object or a primitive type:\n"
1665: + val);
1666: }
1667:
1668: signature = signature_beginning + signature_mid + signature_end;
1669: methods = interpreterRef.methodsByName("defineVariable",
1670: signature); // JDK 1.5 will eliminate this warning
1671: if (methods.size() <= 0) {
1672: throw new DebugException(
1673: "Could not find defineVariable method.");
1674: }
1675:
1676: // Make sure we have a concrete method
1677: Method tempMethod = methods.get(0);
1678: for (int i = 1; i < methods.size() && tempMethod.isAbstract(); i++) {
1679: tempMethod = methods.get(i);
1680: }
1681: if (tempMethod.isAbstract()) {
1682: throw new DebugException(
1683: "Could not find concrete defineVariable method.");
1684: }
1685:
1686: return tempMethod;
1687: }
1688:
1689: /** Assumes that this method is only called immedeately after suspending a thread. */
1690: private ObjectReference _getDebugInterpreter()
1691: throws InvalidTypeException, ClassNotLoadedException,
1692: IncompatibleThreadStateException, InvocationException,
1693: DebugException {
1694:
1695: ThreadReference threadRef = _suspendedThreads.peek();
1696: String interpreterName = _getUniqueThreadName(threadRef);
1697: return _getDebugInterpreter(interpreterName, threadRef);
1698: }
1699:
1700: /** Gets the debug interpreter with the given name using the given suspended thread to invoke methods.
1701: * @param interpreterName Name of the interpreter in the InterpreterJVM
1702: * @param threadRef Suspended thread to use for invoking methods
1703: * @throws IllegalStateException if threadRef is not suspended
1704: */
1705: private ObjectReference _getDebugInterpreter(
1706: String interpreterName, ThreadReference threadRef)
1707: throws InvalidTypeException, ClassNotLoadedException,
1708: IncompatibleThreadStateException, InvocationException,
1709: DebugException {
1710:
1711: if (!threadRef.isSuspended()) {
1712: throw new IllegalStateException(
1713: "threadRef must be suspended to get a debug interpreter.");
1714: }
1715:
1716: // Get the method to return the interpreter
1717: Method m = _getMethod(_interpreterJVM.referenceType(),
1718: "getJavaInterpreter");
1719:
1720: // invokeMethod would throw an ObjectCollectedException if the StringReference
1721: // declared by _vm.mirrorOf(name) had been garbage collected before
1722: // invokeMethod could execute. We now just disable collection until after the
1723: // method is invoked.
1724:
1725: int tries = 0;
1726: StringReference sr = null;
1727: while (tries < OBJECT_COLLECTED_TRIES) {
1728: try {
1729: LinkedList<StringReference> args = new LinkedList<StringReference>(); //Added parameterization <StringReference>.
1730: sr = _vm.mirrorOf(interpreterName);
1731: sr.disableCollection();
1732: args.add(sr); // make the String a JDI Value
1733: _log.log("Invoking " + m.toString() + " on "
1734: + args.toString());
1735: _log.log("Thread is " + threadRef.toString()
1736: + " <suspended = " + threadRef.isSuspended()
1737: + ">");
1738:
1739: ObjectReference tmpInterpreter = (ObjectReference) _interpreterJVM
1740: .invokeMethod(threadRef, m, args,
1741: ObjectReference.INVOKE_SINGLE_THREADED);
1742:
1743: _log.log("Returning...");
1744: return tmpInterpreter;
1745: } catch (ObjectCollectedException e) {
1746: tries++;
1747: } finally {
1748: sr.enableCollection();
1749: }
1750: }
1751: throw new DebugException("The debugInterpreter: "
1752: + interpreterName
1753: + " could not be obtained from interpreterJVM");
1754: }
1755:
1756: /**
1757: * Notifies the debugger that an assignment has been made in
1758: * the given debug interpreter.
1759: *
1760: * Not currently used.
1761: *
1762: * @param name the name of the interpreter
1763: *
1764: public void notifyDebugInterpreterAssignment(String name) {
1765: //System.out.println("notifyDebugInterpreterAssignment(" + name + ")");
1766: }*/
1767:
1768: /**
1769: * Copy the current selected thread's visible variables (those in scope) into
1770: * an interpreter's environment and then switch the Interactions window's
1771: * interpreter to that interpreter.
1772: */
1773: private void _dumpVariablesIntoInterpreterAndSwitch()
1774: throws DebugException, AbsentInformationException {
1775: _log.log(this
1776: + " invoked dumpVariablesIntoInterpreterAndSwitch");
1777: try {
1778: ThreadReference suspendedThreadRef = _suspendedThreads
1779: .peek();
1780: StackFrame frame = suspendedThreadRef.frame(0);
1781: Location l = frame.location();
1782: ReferenceType rt = l.declaringType();
1783: String className = rt.name();
1784:
1785: // Name the new interpreter based on this thread
1786: String interpreterName = _getUniqueThreadName(suspendedThreadRef);
1787: _model.getInteractionsModel().addDebugInterpreter(
1788: interpreterName, className);
1789: ObjectReference debugInterpreter = _getDebugInterpreter();
1790: _log
1791: .log(this
1792: + " executing: frame = suspendedThreadRef.frame(0);");
1793: frame = suspendedThreadRef.frame(0);
1794:
1795: List<LocalVariable> vars = frame.visibleVariables(); // JDK 1.5 will eliminate this warning
1796: Iterator<LocalVariable> varsIterator = vars.iterator();
1797:
1798: _log.log(this + " got visibleVariables");
1799:
1800: // Define each variable
1801: while (varsIterator.hasNext()) {
1802: LocalVariable localVar = varsIterator.next();
1803: _log.log(this + " defined local variable: " + localVar);
1804: // Have to update the frame each time
1805: frame = suspendedThreadRef.frame(0);
1806: Value val = frame.getValue(localVar);
1807: Type type;
1808: if (val != null) {
1809: type = val.type();
1810: } else {
1811: try {
1812: type = localVar.type();
1813: } catch (ClassNotLoadedException e) {
1814: List<ReferenceType> classes = _vm
1815: .classesByName(localVar.typeName()); //JDK 1.5 will eliminate this warning
1816: if (!classes.isEmpty()) {
1817: type = classes.get(0);
1818: } else {
1819: type = null;
1820: }
1821: }
1822: }
1823: _defineVariable(suspendedThreadRef, debugInterpreter,
1824: localVar.name(), val, type);
1825: }
1826:
1827: // Update the frame
1828: frame = suspendedThreadRef.frame(0);
1829:
1830: // Define "this"
1831: Value this Val = frame.this Object();
1832: if (this Val != null) {
1833: _defineVariable(suspendedThreadRef, debugInterpreter,
1834: "this", this Val, this Val.type());
1835: //_setThisInInterpreter(suspendedThreadRef, debugInterpreter, thisVal);
1836: }
1837:
1838: // Set the new interpreter and prompt
1839: String prompt = _getPromptString(suspendedThreadRef);
1840: _log.log(this + " is setting active interpreter");
1841: _model.getInteractionsModel().setActiveInterpreter(
1842: interpreterName, prompt);
1843: } catch (InvalidTypeException exc) {
1844: throw new DebugException(exc.toString());
1845: } catch (IncompatibleThreadStateException e2) {
1846: throw new DebugException(e2.toString());
1847: } catch (ClassNotLoadedException e3) {
1848: throw new DebugException(e3.toString());
1849: } catch (InvocationException e4) {
1850: throw new DebugException(e4.toString());
1851: }
1852: }
1853:
1854: /**
1855: * @return the prompt to display in the itneractions console
1856: * based upon the ThreadReference threadRef, which is being debugged.
1857: */
1858: private String _getPromptString(ThreadReference threadRef) {
1859: return "[" + threadRef.name() + "] > ";
1860: }
1861:
1862: /**
1863: * Defines a variable with the given name to the given value, using
1864: * a thread reference and JavaInterpreter.
1865: * If type == null, we assume that the type of this variable
1866: * has not been loaded so we will set it to Object in DynamicJavaAdapter.
1867: * @param suspendedThreadRef Thread ref being debugged
1868: * @param debugInterpreter ObjectReference to the JavaInterpreter to contain
1869: * the variable
1870: * @param name Name of the variable
1871: * @param val Value of the variable
1872: */
1873: private void _defineVariable(ThreadReference suspendedThreadRef,
1874: ObjectReference debugInterpreter, String name, Value val,
1875: Type type) throws InvalidTypeException,
1876: IncompatibleThreadStateException, ClassNotLoadedException,
1877: InvocationException, DebugException {
1878: ReferenceType rtDebugInterpreter = debugInterpreter
1879: .referenceType();
1880: Method method2Call = _getDefineVariableMethod(
1881: rtDebugInterpreter, val);
1882:
1883: // invokeMethod would throw an ObjectCollectedException if the StringReference
1884: // declared by _vm.mirrorOf(name) had been garbage collected before
1885: // invokeMethod could execute. We now just disable collection until after the
1886: // method is invoked.
1887:
1888: int tries = 0;
1889: StringReference sr = null;
1890: while (tries < OBJECT_COLLECTED_TRIES) {
1891: try {
1892: //Added parameterization <Value>.
1893: List<Value> args = new LinkedList<Value>();
1894: /* Mirror is the common supertype of StringReference, Value, and ReferenceType. Changed from Mirror to Value
1895: * because invokeMethod requires a List of Value type. It does not need to be a Mirror because neither sr nor
1896: * val can be a ReferenceType */
1897: sr = _vm.mirrorOf(name);
1898: sr.disableCollection();
1899: args.add(sr);
1900: args.add(val);
1901: if (type == null)
1902: args.add(null);
1903: else if (type instanceof ReferenceType) {
1904: args.add(((ReferenceType) type).classObject());
1905: }
1906:
1907: /* System.out.println("Calling " + method2Call.toString() + "with " + args.get(0).toString()); */
1908: debugInterpreter.invokeMethod(suspendedThreadRef,
1909: method2Call, args,
1910: ObjectReference.INVOKE_SINGLE_THREADED);
1911: return;
1912: } catch (ObjectCollectedException oce) {
1913: tries++;
1914: } finally {
1915: sr.enableCollection();
1916: }
1917: }
1918: throw new DebugException("The variable: " + name
1919: + " could not be defined in the debug interpreter");
1920: }
1921:
1922: /** Notifies all listeners that the current thread has been suspended. Synchronization is necessary because it is
1923: * called from unsynchronized listeners and other classes (in same package).
1924: */
1925: synchronized void currThreadSuspended() {
1926: try {
1927: try {
1928: // copy the variables in scope into an interpreter
1929: // and switch the current interpreter to that interpreter
1930: _dumpVariablesIntoInterpreterAndSwitch();
1931: _switchToSuspendedThread();
1932: } catch (AbsentInformationException aie) {
1933: // an AbsentInformationException can be thrown if the user does not
1934: // compile the classes to be debugged with the -g flag
1935: printMessage("No debug information available for this class.\nMake sure to compile classes to be debugged with the -g flag.");
1936: _hideWatches();
1937: // don't updateWatches in _switchToSuspendedThread since it will display the default
1938: // interpreter's watch information.
1939: _switchToSuspendedThread(false);
1940: }
1941: } catch (DebugException de) {
1942: throw new UnexpectedException(de);
1943: }
1944: }
1945:
1946: /** Calls the real switchToSuspendedThread, telling it to updateWatches. This is what is usually called. */
1947: private void _switchToSuspendedThread() throws DebugException {
1948: _switchToSuspendedThread(true);
1949: }
1950:
1951: /** Performs the bookkeeping to switch to the suspened thread on the top of the _suspendedThreads stack.
1952: * @param updateWatches this is false if the current file does not have debug information. This prevents the default
1953: * interpreter's watch values from being shown.
1954: */
1955: private void _switchToSuspendedThread(boolean updateWatches)
1956: throws DebugException {
1957: _log.log(this + " executing _switchToSuspendedThread()");
1958: _runningThread = null;
1959: if (updateWatches)
1960: _updateWatches();
1961: final ThreadReference currThread = _suspendedThreads.peek();
1962: _notifier.currThreadSuspended();
1963: // Anytime a thread is suspended, it becomes the current thread.
1964: // This makes sure the debug panel will correctly put the
1965: // current thread in bold.
1966: _notifier.currThreadSet(new JPDAThreadData(currThread));
1967:
1968: try {
1969: if (currThread.frameCount() > 0) {
1970: scrollToSource(currThread.frame(0).location());
1971: }
1972: } catch (IncompatibleThreadStateException itse) {
1973: throw new UnexpectedException(itse);
1974: }
1975: }
1976:
1977: /**
1978: * Returns a unique name for the given thread.
1979: */
1980: private String _getUniqueThreadName(ThreadReference thread) {
1981: return Long.toString(thread.uniqueID());
1982: }
1983:
1984: /**
1985: * @return the Method corresponding to DynamicJavaAdapter.getVariable()
1986: */
1987: private Method _getGetVariableMethod(ReferenceType rtInterpreter) {
1988: return _getMethod(rtInterpreter, "getVariable");
1989: }
1990:
1991: /**
1992: * Returns the concrete method with the given name on the reference type.
1993: * @param rt ReferenceType containing the method
1994: * @param name Name of the method
1995: * @throws NoSuchElementException if no concrete method could be found
1996: */
1997: private Method _getMethod(ReferenceType rt, String name) {
1998: List<Method> methods = rt.methodsByName(name); // JDK 1.5 will eliminate this warning
1999: Iterator<Method> methodsIterator = methods.iterator();
2000:
2001: // iterate over all the methods in the list and return the first non-abstract one
2002: while (methodsIterator.hasNext()) {
2003: Method m = methodsIterator.next();
2004: if (!m.isAbstract()) {
2005: return m;
2006: }
2007: }
2008:
2009: throw new NoSuchElementException(
2010: "No non-abstract method called " + name + " found in "
2011: + rt.name());
2012: }
2013:
2014: /**
2015: * Converts a primitive wrapper object (eg. Integer) to its corresponding
2016: * primitive value (eg. int) by invoking the appropriate method in the
2017: * given thread.
2018: * @param threadRef Thread in which to invoke the method
2019: * @param localVar Variable to convert
2020: * @param v Value of localVar
2021: * @return Converted primitive, or v if it was a reference type
2022: */
2023: private Value _convertToActualType(ThreadReference threadRef,
2024: LocalVariable localVar, Value v)
2025: throws InvalidTypeException, ClassNotLoadedException,
2026: IncompatibleThreadStateException, InvocationException {
2027: String typeSignature;
2028: try {
2029: typeSignature = localVar.type().signature();
2030: } catch (ClassNotLoadedException cnle) {
2031: return v;
2032: }
2033: Method m;
2034: ObjectReference ref = (ObjectReference) v;
2035: ReferenceType rt = ref.referenceType();
2036:
2037: if (typeSignature.equals("Z")) {
2038: m = _getMethod(rt, "booleanValue");
2039: } else if (typeSignature.equals("B")) {
2040: m = _getMethod(rt, "byteValue");
2041: } else if (typeSignature.equals("C")) {
2042: m = _getMethod(rt, "charValue");
2043: } else if (typeSignature.equals("S")) {
2044: m = _getMethod(rt, "shortValue");
2045: } else if (typeSignature.equals("I")) {
2046: m = _getMethod(rt, "intValue");
2047: } else if (typeSignature.equals("J")) {
2048: m = _getMethod(rt, "longValue");
2049: } else if (typeSignature.equals("F")) {
2050: m = _getMethod(rt, "floatValue");
2051: } else if (typeSignature.equals("D")) {
2052: m = _getMethod(rt, "doubleValue");
2053: } else {
2054: return v;
2055: }
2056:
2057: return ref.invokeMethod(threadRef, m, new LinkedList<Value>(),
2058: ObjectReference.INVOKE_SINGLE_THREADED);
2059: }
2060:
2061: // private ClassObjectReference _getClassForName(String name, ThreadReference thread, ClassLoaderReference clr)
2062: // throws InvalidTypeException, ClassNotLoadedException, AbsentInformationException,
2063: // IncompatibleThreadStateException, InvocationException, DebugException
2064: // {
2065: // Value v = null;
2066: //
2067: // // invokeMethod would throw an ObjectCollectedException if the StringReference
2068: // // declared by _vm.mirrorOf(name) had been garbage collected before
2069: // // invokeMethod could execute. This happened infrequently so by trying this
2070: // // multiple times, the chance of failure each time should be acceptably low.
2071: // int tries = 0;
2072: // while (tries < MAXINVOKETRIES) {
2073: // try {
2074: // ReferenceType rt = clr.referenceType();
2075: // Method method2Call = _getMethod(rt, "loadClass");
2076: // List args = new LinkedList();
2077: // args.add(_vm.mirrorOf(name));
2078: // args.add(_vm.mirrorOf(true));
2079: // v = clr.invokeMethod(thread, method2Call, args,
2080: // ObjectReference.INVOKE_SINGLE_THREADED);
2081: // break;
2082: // }
2083: // catch (ObjectCollectedException oce) {
2084: // _log.log("Got ObjectCollectedException");
2085: // tries++;
2086: // }
2087: // }
2088: // if (v != null) {
2089: // //v = _convertToActualType(thread, var, v);
2090: // return (ClassObjectReference)v;
2091: // }
2092: //
2093: // return null;
2094: // }
2095:
2096: private Value _getValueOfLocalVariable(LocalVariable var,
2097: ThreadReference thread) throws InvalidTypeException,
2098: ClassNotLoadedException, IncompatibleThreadStateException,
2099: InvocationException, DebugException {
2100: ObjectReference interpreter = _getDebugInterpreter(
2101: _getUniqueThreadName(thread), thread);
2102: ReferenceType rtInterpreter = interpreter.referenceType();
2103: Method method2Call = _getGetVariableMethod(rtInterpreter);
2104:
2105: // invokeMethod would throw an ObjectCollectedException if the StringReference
2106: // declared by _vm.mirrorOf(name) had been garbage collected before
2107: // invokeMethod could execute. We now just disable collection until after the
2108: // method is invoked.
2109:
2110: int tries = 0;
2111: StringReference sr = null;
2112: String varName = var.name();
2113: while (tries < OBJECT_COLLECTED_TRIES) {
2114: try {
2115: List<Value> args = new LinkedList<Value>(); //Added parameterization <Value>
2116: sr = _vm.mirrorOf(varName);
2117: sr.disableCollection();
2118: args.add(sr);
2119: Value v = interpreter.invokeMethod(thread, method2Call,
2120: args, ObjectReference.INVOKE_SINGLE_THREADED);
2121: if (v != null)
2122: v = _convertToActualType(thread, var, v);
2123:
2124: return v;
2125: } catch (ObjectCollectedException oce) {
2126: tries++;
2127: } finally {
2128: sr.enableCollection();
2129: }
2130: }
2131: throw new DebugException("The value of variable: " + varName
2132: + " could not be obtained from the debug interpreter");
2133:
2134: }
2135:
2136: /** Copies the variables in the current interpreter back into the Threaf it refers to. */
2137: private void _copyBack(ThreadReference threadRef)
2138: throws IncompatibleThreadStateException,
2139: AbsentInformationException, InvocationException,
2140: DebugException {
2141: _log.log("Getting debug interpreter");
2142: _log.log("Getting variables");
2143: StackFrame frame = threadRef.frame(0);
2144: List<LocalVariable> vars = frame.visibleVariables(); // Added <LocalVariable> type argument; warning will go away in JDK 1.5
2145: Iterator<LocalVariable> varsIterator = vars.iterator();
2146:
2147: // Get each variable from the stack frame
2148: while (varsIterator.hasNext()) {
2149: _log.log("Iterating through vars");
2150: LocalVariable localVar = varsIterator.next();
2151:
2152: try {
2153: Value v = _getValueOfLocalVariable(localVar, threadRef);
2154: frame = threadRef.frame(0);
2155: frame.setValue(localVar, v);
2156: } catch (ClassNotLoadedException cnle) {
2157: printMessage("Could not update the value of '"
2158: + localVar.name() + "' (class not loaded)");
2159: } catch (InvalidTypeException ite) {
2160: printMessage("Could not update the value of '"
2161: + localVar.name()
2162: + "' (invalid type exception)");
2163: }
2164: }
2165: }
2166:
2167: /** Assumes lock is already held. */
2168: private void _copyVariablesFromInterpreter() throws DebugException {
2169: try {
2170: // copy variables values out of interpreter's environment and
2171: // into the relevant stack frame
2172: _log.log("In _copyBack()");
2173: _copyBack(_runningThread);
2174: _log.log("Out of _copyBack()");
2175: } catch (AbsentInformationException e2) {
2176: //throw new DebugException(e2.toString());
2177: // Silently fail for now to ignore the AbsentInformationException that
2178: // we should have noticed when first suspending on this line (see currThreadSuspended).
2179: } catch (IncompatibleThreadStateException e) {
2180: throw new DebugException(e.toString());
2181: } catch (InvocationException e4) {
2182: throw new DebugException(e4.toString());
2183: }
2184: }
2185:
2186: /** Removes all of the debug interpreters as part of shutting down. Assumes lock is already held. */
2187: private void _removeAllDebugInterpreters() {
2188: DefaultInteractionsModel interactionsModel = _model
2189: .getInteractionsModel();
2190: String oldInterpreterName;
2191: if (_runningThread != null) {
2192: oldInterpreterName = _getUniqueThreadName(_runningThread);
2193: interactionsModel.removeInterpreter(oldInterpreterName);
2194: }
2195: while (!_suspendedThreads.isEmpty()) {
2196: ThreadReference threadRef = _suspendedThreads.pop();
2197: oldInterpreterName = _getUniqueThreadName(threadRef);
2198: interactionsModel.removeInterpreter(oldInterpreterName);
2199: }
2200: }
2201:
2202: /** This method is called to remove the current debug interpreter upon resuming the current thread. Assumes that
2203: * lock on this is alaredy held.
2204: * @param fromStep If true, switch to the default interpreter since we don't want to switch to the next debug
2205: * interpreter and display its watch data. We would like to just not have an active interpreter and put up an
2206: * hourglass over the interactions pane, but the interpreterJVM must have an active interpreter.
2207: */
2208: private void _removeCurrentDebugInterpreter(boolean fromStep) {
2209: DefaultInteractionsModel interactionsModel = _model
2210: .getInteractionsModel();
2211: // switch to next interpreter on the stack
2212: if (fromStep || _suspendedThreads.isEmpty()) {
2213: interactionsModel.setToDefaultInterpreter();
2214: } else {
2215: ThreadReference threadRef = _suspendedThreads.peek();
2216: _switchToInterpreterForThreadReference(threadRef);
2217: }
2218: String oldInterpreterName = _getUniqueThreadName(_runningThread);
2219: interactionsModel.removeInterpreter(oldInterpreterName);
2220: }
2221:
2222: /** Notifies all listeners that the current thread has been resumed. Synchronization dropped because invokeLater runs
2223: * asynchronously.
2224: * Precondition: Assumes that the current thread hasn't yet been resumed
2225: */
2226: private void currThreadResumed() throws DebugException {
2227: _log.log(this + " is executing currThreadResumed()");
2228: Utilities.invokeLater(new Runnable() {
2229: public void run() {
2230: _notifier.currThreadResumed();
2231: }
2232: });
2233: }
2234:
2235: /** Switches the current interpreter to the one corresponding to threadRef. Assumes lock on this is already held.
2236: * @param threadRef The ThreadRefernce corresponding to the interpreter to switch to
2237: */
2238: private void _switchToInterpreterForThreadReference(
2239: ThreadReference threadRef) {
2240: String threadName = _getUniqueThreadName(threadRef);
2241: String prompt = _getPromptString(threadRef);
2242: _model.getInteractionsModel().setActiveInterpreter(threadName,
2243: prompt);
2244: }
2245:
2246: /** Not synchronized because invokeLater is asynchronous. */
2247: void threadStarted() {
2248: Utilities.invokeLater(new Runnable() {
2249: public void run() {
2250: _notifier.threadStarted();
2251: }
2252: });
2253: }
2254:
2255: /** Notifies all listeners that the current thread has died. updateThreads is set to true if the threads and stack
2256: * tables need to be updated, false if there are no suspended threads
2257: */
2258: synchronized void currThreadDied() throws DebugException {
2259: printMessage("The current thread has finished.");
2260: _runningThread = null;
2261:
2262: _updateWatches();
2263:
2264: if (_suspendedThreads.size() > 0) {
2265: ThreadReference thread = _suspendedThreads.peek();
2266: _switchToInterpreterForThreadReference(thread);
2267:
2268: try {
2269: if (thread.frameCount() <= 0) {
2270: printMessage("Could not scroll to source for "
2271: + thread.name()
2272: + ". It has no stackframes.");
2273: } else
2274: scrollToSource(thread.frame(0).location());
2275: } catch (IncompatibleThreadStateException e) {
2276: throw new UnexpectedException(e);
2277: }
2278:
2279: // updates watches and makes buttons in UI active, does this because
2280: // there are suspended threads on the stack
2281: _switchToSuspendedThread();
2282: }
2283: Utilities.invokeLater(new Runnable() {
2284: public void run() {
2285: _notifier.currThreadDied();
2286: }
2287: });
2288: }
2289:
2290: void nonCurrThreadDied() {
2291: Utilities.invokeLater(new Runnable() {
2292: public void run() {
2293: _notifier.nonCurrThreadDied();
2294: }
2295: });
2296: }
2297:
2298: /** Notifies all listeners that the debugger has shut down. updateThreads is set to true if the threads and stack
2299: * tables need to be updated, false if there are no suspended threads
2300: */
2301: void notifyDebuggerShutdown() {
2302: Utilities.invokeLater(new Runnable() {
2303: public void run() {
2304: _notifier.debuggerShutdown();
2305: }
2306: });
2307: }
2308:
2309: /** Notifies all listeners that the debugger has started. */
2310: void notifyDebuggerStarted() {
2311: Utilities.invokeLater(new Runnable() {
2312: public void run() {
2313: _notifier.debuggerStarted();
2314: }
2315: });
2316: }
2317:
2318: /** Notifies all listeners that a step has been requested. */
2319: void notifyStepRequested() {
2320: Utilities.invokeLater(new Runnable() {
2321: public void run() {
2322: _notifier.stepRequested();
2323: }
2324: });
2325: }
2326:
2327: /** A thread-safe stack from which you can remove any element, not just the top of the stack. All synchronization is
2328: * performed on the wrapped vector.
2329: * TODO: make a generic Collection extending/replacing Stack.
2330: */
2331: private static class RandomAccessStack extends
2332: Stack<ThreadReference> {
2333:
2334: public ThreadReference peekAt(int i) {
2335: return get(i);
2336: }
2337:
2338: public ThreadReference remove(long id)
2339: throws NoSuchElementException {
2340: synchronized (this ) {
2341: for (int i = 0; i < size(); i++) {
2342: if (get(i).uniqueID() == id) {
2343: ThreadReference t = get(i);
2344: remove(i);
2345: return t;
2346: }
2347: }
2348: }
2349:
2350: throw new NoSuchElementException("Thread " + id
2351: + " not found in debugger suspended threads stack!");
2352: }
2353:
2354: public synchronized boolean contains(long id) {
2355: for (int i = 0; i < size(); i++) {
2356: if (get(i).uniqueID() == id)
2357: return true;
2358: }
2359: return false;
2360: }
2361:
2362: public boolean isEmpty() {
2363: return empty();
2364: }
2365: }
2366:
2367: // /** This class tries to filter out system threads. It is currently unused. */
2368: // class SystemThreadsFilter{
2369: // private HashMap<String,Boolean> _filterThese;
2370: //
2371: // public SystemThreadsFilter(List threads) {
2372: // _filterThese = new HashMap<String,Boolean>();
2373: // Iterator iterator = threads.iterator();
2374: // String temp = null;
2375: //
2376: // while(iterator.hasNext()) {
2377: // temp = ((ThreadReference)iterator.next()).name();
2378: // _filterThese.put(temp, Boolean.TRUE);
2379: // }
2380: // }
2381: //
2382: // public List filter(List list) {
2383: // LinkedList retList = new LinkedList();
2384: // String temp = null;
2385: // ThreadReference tempThreadRef = null;
2386: // Iterator iterator = list.iterator();
2387: //
2388: // while(iterator.hasNext()) {
2389: // tempThreadRef = (ThreadReference)iterator.next();
2390: // temp = tempThreadRef.name();
2391: // if ( _filterThese.get(temp) == null ) {
2392: // retList.add(tempThreadRef);
2393: // }
2394: // }
2395: //
2396: // return retList;
2397: // }
2398: // }
2399: }
|