blob: cebb41e45c5e9da58f7a3f5292a4d26ab2c5af57 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* BEA - Daniel R Somerfield - Bug 89643
*******************************************************************************/
package org.eclipse.jdt.internal.debug.core.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.ITerminate;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.debug.core.IEvaluationRunnable;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaThreadGroup;
import org.eclipse.jdt.debug.core.IJavaValue;
import org.eclipse.jdt.debug.core.IJavaVariable;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.internal.debug.core.IJDIEventListener;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint;
import com.ibm.icu.text.MessageFormat;
import com.sun.jdi.BooleanValue;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.ClassType;
import com.sun.jdi.Field;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.InvalidStackFrameException;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.InvocationException;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadGroupReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.Value;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.StepEvent;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.StepRequest;
/**
* Model thread implementation for an underlying
* thread on a VM.
*/
public class JDIThread extends JDIDebugElement implements IJavaThread {
/**
* Constant for the name of the default Java stratum
*/
private static final String JAVA_STRATUM_CONSTANT = "Java"; //$NON-NLS-1$
/**
* Constant for the name of the main thread group.
*/
private static final String MAIN_THREAD_GROUP = "main"; //$NON-NLS-1$
/**
* Status code indicating that a request to suspend this thread has timed out
*/
public static final int SUSPEND_TIMEOUT= 161;
/**
* Underlying thread.
*/
private ThreadReference fThread;
/**
* Cache of previous name, used in case thread is garbage collected.
*/
private String fPreviousName;
/**
* Collection of stack frames
*/
private List fStackFrames;
/**
* Underlying thread group, cached on first access.
*/
private ThreadGroupReference fThreadGroup;
/**
* Name of underlying thread group, cached on first access.
*/
private String fThreadGroupName;
/**
* Whether children need to be refreshed. Set to
* <code>true</code> when stack frames are re-used
* on the next suspend.
*/
private boolean fRefreshChildren = true;
/**
* Currently pending step handler, <code>null</code>
* when not performing a step.
*/
private StepHandler fStepHandler= null;
/**
* Whether running.
*/
private boolean fRunning;
/**
* Whether terminated.
*/
private boolean fTerminated;
/**
* whether suspended but without firing the equivalent events.
*/
private boolean fSuspendedQuiet;
/**
* Whether this thread is a system thread.
*/
private boolean fIsSystemThread;
/**
* Whether this thread is a daemon thread
* @since 3.3
*/
private boolean fIsDaemon = false;
/**
* The collection of breakpoints that caused the last suspend, or
* an empty collection if the thread is not suspended or was not
* suspended by any breakpoint(s).
*/
private List fCurrentBreakpoints = new ArrayList(2);
/**
* Whether this thread is currently performing
* an evaluation. An evaluation may involve a series
* of method invocations.
*/
private boolean fIsPerformingEvaluation= false;
private IEvaluationRunnable fEvaluationRunnable;
/**
* Whether this thread was manually suspended during an
* evaluation.
*/
private boolean fEvaluationInterrupted = false;
/**
* Whether this thread is currently invoking a method.
* Nested method invocations cannot be performed.
*/
private boolean fIsInvokingMethod = false;
/**
* Whether or not this thread is currently honoring
* breakpoints. This flag allows breakpoints to be
* disabled during evaluations.
*/
private boolean fHonorBreakpoints= true;
/**
* The kind of step that was originally requested. Zero or
* more 'secondary steps' may be performed programmatically after
* the original user-requested step, and this field tracks the
* type (step into, over, return) of the original step.
*/
private int fOriginalStepKind;
/**
* The JDI Location from which an original user-requested step began.
*/
private Location fOriginalStepLocation;
/**
* The total stack depth at the time an original (user-requested) step
* is initiated. This is used along with the original step Location
* to determine if a step into comes back to the starting location and
* needs to be 'nudged' forward. Checking the stack depth eliminates
* undesired 'nudging' in recursive methods.
*/
private int fOriginalStepStackDepth;
/**
* Whether or not this thread is currently suspending (user-requested).
*/
private boolean fIsSuspending= false;
/**
* Whether or not this thread is currently evaluating an expression for a conditional breakpoint
*/
private boolean fIsEvaluatingConditionalBreakpoint= false;
private ThreadJob fAsyncJob;
private ThreadJob fRunningAsyncJob;
/**
* Creates a new thread on the underlying thread reference
* in the given debug target.
*
* @param target the debug target in which this thread is contained
* @param thread the underlying thread on the VM
* @exception ObjectCollectedException if the underlying thread has been
* garbage collected and cannot be properly initialized
*/
public JDIThread(JDIDebugTarget target, ThreadReference thread) throws ObjectCollectedException {
super(target);
setUnderlyingThread(thread);
initialize();
}
/**
* Thread initialization:<ul>
* <li>Determines if this thread is a system thread</li>
* <li>Sets terminated state to <code>false</code></li>
* <li>Determines suspended state from underlying thread</li>
* <li>Sets this threads stack frames to an empty collection</li>
* </ul>
* @exception ObjectCollectedException if the thread has been garbage
* collected and cannot be initialized
*/
protected void initialize() throws ObjectCollectedException {
fStackFrames= new ArrayList();
// system thread
try {
determineIfSystemThread();
} catch (DebugException e) {
Throwable underlyingException= e.getStatus().getException();
if (underlyingException instanceof VMDisconnectedException) {
// Threads may be created by the VM at shutdown
// as finalizers. The VM may be disconnected by
// the time we hear about the thread creation.
disconnected();
return;
}
if (underlyingException instanceof ObjectCollectedException) {
throw (ObjectCollectedException)underlyingException;
}
logError(e);
}
try {
determineIfDaemonThread();
} catch (DebugException e) {
Throwable underlyingException= e.getStatus().getException();
if (underlyingException instanceof VMDisconnectedException) {
// Threads may be created by the VM at shutdown
// as finalizers. The VM may be disconnected by
// the time we hear about the thread creation.
disconnected();
return;
}
logError(e);
}
try {
ThreadGroupReference group = getUnderlyingThreadGroup();
// might already be terminated
if (group != null) {
getJavaDebugTarget().addThreadGroup(group);
}
} catch (DebugException e1) {
}
// state
setTerminated(false);
setRunning(false);
try {
// see bug 30816
if (fThread.status() == ThreadReference.THREAD_STATUS_UNKNOWN) {
setRunning(true);
return;
}
} catch (VMDisconnectedException e) {
disconnected();
return;
} catch (ObjectCollectedException e){
throw e;
} catch (RuntimeException e) {
logError(e);
}
try {
boolean suspended = fThread.isSuspended();
if (suspended) {
// Unless we're at a breakpoint, set status to running. The thread state
// will be properly updated to suspended in the case that a breakpoint
// is hit. Otherwise this is likely just a transient suspend state (for
// example, a breakpoint is handling a class prepare event quietly).
// See bug 120385
suspended = getBreakpoints().length > 0;
}
setRunning(!suspended);
} catch (VMDisconnectedException e) {
disconnected();
return;
} catch (ObjectCollectedException e){
throw e;
} catch (RuntimeException e) {
logError(e);
}
}
/**
* Adds the given breakpoint to the list of breakpoints
* this thread is suspended at
*/
protected void addCurrentBreakpoint(IBreakpoint bp) {
fCurrentBreakpoints.add(bp);
}
/**
* Removes the given breakpoint from the list of breakpoints
* this thread is suspended at (called when a breakpoint is
* deleted, in case we are suspended at that breakpoint)
*/
protected void removeCurrentBreakpoint(IBreakpoint bp) {
fCurrentBreakpoints.remove(bp);
}
/**
* @see org.eclipse.debug.core.model.IThread#getBreakpoints()
*/
public synchronized IBreakpoint[] getBreakpoints() {
return (IBreakpoint[])fCurrentBreakpoints.toArray(new IBreakpoint[fCurrentBreakpoints.size()]);
}
/**
* @see ISuspendResume#canResume()
*/
public boolean canResume() {
return isSuspended() && !isSuspendedQuiet() && (!isPerformingEvaluation() || isInvokingMethod());
}
/**
* @see ISuspendResume#canSuspend()
*/
public boolean canSuspend() {
return !isSuspended() || isSuspendedQuiet() || (isPerformingEvaluation() && !isInvokingMethod());
}
/**
* @see ITerminate#canTerminate()
*/
public boolean canTerminate() {
return getDebugTarget().canTerminate();
}
/**
* @see IStep#canStepInto()
*/
public boolean canStepInto() {
return canStep();
}
/**
* @see IStep#canStepOver()
*/
public boolean canStepOver() {
return canStep();
}
/**
* @see IStep#canStepReturn()
*/
public boolean canStepReturn() {
return canStep();
}
/**
* Returns whether this thread is in a valid state to
* step.
*
* @return whether this thread is in a valid state to
* step
*/
protected boolean canStep() {
try {
return isSuspended()
&& !isSuspendedQuiet()
&& (!isPerformingEvaluation() || isInvokingMethod())
&& !isStepping()
&& getTopStackFrame() != null
&& !getJavaDebugTarget().isPerformingHotCodeReplace();
} catch (DebugException e) {
return false;
}
}
/**
* Determines and sets whether this thread represents a system thread.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void determineIfSystemThread() throws DebugException {
fIsSystemThread= false;
ThreadGroupReference tgr= getUnderlyingThreadGroup();
fIsSystemThread = tgr != null;
while (tgr != null) {
String tgn= null;
try {
tgn= tgr.name();
tgr= tgr.parent();
} catch (UnsupportedOperationException e) {
fIsSystemThread = false;
break;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_determining_if_system_thread, new String[] {e.toString()}), e);
// execution will not reach this line, as
// #targetRequestFailed will throw an exception
return;
}
if (tgn != null && tgn.equals(MAIN_THREAD_GROUP)) {
fIsSystemThread= false;
break;
}
}
}
/**
* Determines whether this is a daemon thread.
*
* @throws DebugException on failure
*/
protected void determineIfDaemonThread() throws DebugException {
fIsDaemon = false;
try {
ReferenceType referenceType = getUnderlyingThread().referenceType();
Field field = referenceType.fieldByName("daemon"); //$NON-NLS-1$
if (field == null) {
field = referenceType.fieldByName("isDaemon"); //$NON-NLS-1$
}
if (field != null) {
if (field.signature().equals(Signature.SIG_BOOLEAN)) {
Value value = getUnderlyingThread().getValue(field);
if (value instanceof BooleanValue) {
fIsDaemon = ((BooleanValue)value).booleanValue();
}
}
}
}
catch(ObjectCollectedException oce) {/*do nothing thread does not exist*/}
catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_47, e);
}
}
/**
* NOTE: this method returns a copy of this thread's stack frames.
*
* @see IThread#getStackFrames()
*/
public synchronized IStackFrame[] getStackFrames() throws DebugException {
if (isSuspendedQuiet()) {
return new IStackFrame[0];
}
List list = computeStackFrames();
return (IStackFrame[])list.toArray(new IStackFrame[list.size()]);
}
/**
* @see computeStackFrames()
*
* @param refreshChildren whether or not this method should request new stack
* frames from the VM
*/
protected synchronized List computeStackFrames(boolean refreshChildren) throws DebugException {
if (isSuspended()) {
if (isTerminated()) {
fStackFrames.clear();
} else if (refreshChildren) {
List frames = getUnderlyingFrames();
int oldSize = fStackFrames.size();
int newSize = frames.size();
int discard = oldSize - newSize; // number of old frames to discard, if any
for (int i = 0; i < discard; i++) {
JDIStackFrame invalid = (JDIStackFrame) fStackFrames.remove(0);
invalid.bind(null, -1);
}
int newFrames = newSize - oldSize; // number of frames to create, if any
int depth = oldSize;
for (int i = newFrames - 1; i >= 0; i--) {
fStackFrames.add(0, new JDIStackFrame(this, (StackFrame) frames.get(i), depth));
depth++;
}
int numToRebind = Math.min(newSize, oldSize); // number of frames to attempt to rebind
int offset = newSize - 1;
for (depth = 0; depth < numToRebind; depth++) {
JDIStackFrame oldFrame = (JDIStackFrame) fStackFrames.get(offset);
StackFrame frame = (StackFrame) frames.get(offset);
JDIStackFrame newFrame = oldFrame.bind(frame, depth);
if (newFrame != oldFrame) {
fStackFrames.set(offset, newFrame);
}
offset--;
}
}
fRefreshChildren = false;
} else {
return Collections.EMPTY_LIST;
}
return fStackFrames;
}
/**
* Returns this thread's current stack frames as a list, computing
* them if required. Returns an empty collection if this thread is
* not currently suspended, or this thread is terminated. This
* method should be used internally to get the current stack frames,
* instead of calling <code>#getStackFrames()</code>, which makes a
* copy of the current list.
* <p>
* Before a thread is resumed a call must be made to one of:<ul>
* <li><code>preserveStackFrames()</code></li>
* <li><code>disposeStackFrames()</code></li>
* </ul>
* If stack frames are disposed before a thread is resumed, stack frames
* are completely re-computed on the next call to this method. If stack
* frames are to be preserved, this method will attempt to re-use any stack
* frame objects which represent the same stack frame as on the previous
* suspend. Stack frames are cached until a subsequent call to preserve
* or dispose stack frames.
* </p>
*
* @return list of <code>IJavaStackFrame</code>
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
public synchronized List computeStackFrames() throws DebugException {
return computeStackFrames(fRefreshChildren);
}
/**
* @see JDIThread#computeStackFrames()
*
* This method differs from computeStackFrames() in that it
* always requests new stack frames from the VM. As this is
* an expensive operation, this method should only be used
* by clients who know for certain that the stack frames
* on the VM have changed.
*/
public List computeNewStackFrames() throws DebugException {
return computeStackFrames(true);
}
private List getUnderlyingFrames() throws DebugException {
if (!isSuspended()) {
// Checking isSuspended here eliminates a race condition in resume
// between the time stack frames are preserved and the time the
// underlying thread is actually resumed.
requestFailed(JDIDebugModelMessages.JDIThread_Unable_to_retrieve_stack_frame___thread_not_suspended__1, null, IJavaThread.ERR_THREAD_NOT_SUSPENDED);
}
try {
return fThread.frames();
} catch (IncompatibleThreadStateException e) {
requestFailed(JDIDebugModelMessages.JDIThread_Unable_to_retrieve_stack_frame___thread_not_suspended__1, e, IJavaThread.ERR_THREAD_NOT_SUSPENDED);
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_retrieving_stack_frames_2, new String[] {e.toString()}), e);
} catch (InternalError e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_retrieving_stack_frames_2, new String[] {e.toString()}), e);
}
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return null;
}
/**
* Returns the number of frames on the stack from the
* underlying thread.
*
* @return number of frames on the stack
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* <li>This thread is not suspended</li>
* </ul>
*/
protected int getUnderlyingFrameCount() throws DebugException {
try {
return fThread.frameCount();
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_retrieving_frame_count, new String[] {e.toString()}), e);
} catch (IncompatibleThreadStateException e) {
requestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_retrieving_frame_count, new String[] {e.toString()}), e, IJavaThread.ERR_THREAD_NOT_SUSPENDED);
}
// execution will not reach here - try block will either
// return or exception will be thrown
return -1;
}
/**
* @see IJavaThread#runEvaluation(IEvaluationRunnable, IProgressMonitor, int, boolean)
*/
public void runEvaluation(IEvaluationRunnable evaluation, IProgressMonitor monitor, int evaluationDetail, boolean hitBreakpoints) throws DebugException {
if (isPerformingEvaluation()) {
requestFailed(JDIDebugModelMessages.JDIThread_Cannot_perform_nested_evaluations, null, IJavaThread.ERR_NESTED_METHOD_INVOCATION); //
}
if (!canRunEvaluation()) {
requestFailed(JDIDebugModelMessages.JDIThread_Evaluation_failed___thread_not_suspended, null, IJavaThread.ERR_THREAD_NOT_SUSPENDED);
}
fIsPerformingEvaluation = true;
fEvaluationRunnable= evaluation;
fHonorBreakpoints= hitBreakpoints;
fireResumeEvent(evaluationDetail);
//save and restore current breakpoint information - bug 30837
IBreakpoint[] breakpoints = getBreakpoints();
ISchedulingRule rule = null;
if (evaluationDetail == DebugEvent.EVALUATION_IMPLICIT) {
rule = getThreadRule();
}
try {
if (rule != null) {
Job.getJobManager().beginRule(rule, monitor);
}
if (monitor == null || !monitor.isCanceled()) {
evaluation.run(this, monitor);
}
} catch (DebugException e) {
throw e;
} finally {
if (rule != null) {
Job.getJobManager().endRule(rule);
}
fIsPerformingEvaluation = false;
fEvaluationRunnable= null;
fHonorBreakpoints= true;
if (getBreakpoints().length == 0 && breakpoints.length > 0) {
for (int i = 0; i < breakpoints.length; i++) {
addCurrentBreakpoint(breakpoints[i]);
}
}
fireSuspendEvent(evaluationDetail);
if (fEvaluationInterrupted && (fAsyncJob == null || fAsyncJob.isEmpty()) && (fRunningAsyncJob == null || fRunningAsyncJob.isEmpty())) {
// @see bug 31585:
// When an evaluation was interrupted & resumed, the launch view does
// not update properly. It cannot know when it is safe to display frames
// since it does not know about queued evaluations. Thus, when the queue
// is empty, we fire a change event to force the view to update.
fEvaluationInterrupted = false;
fireChangeEvent(DebugEvent.CONTENT);
}
}
}
/**
* Returns whether this thread is in a valid state to
* run an evaluation.
*
* @return whether this thread is in a valid state to
* run an evaluation
*/
protected boolean canRunEvaluation() {
// NOTE similar to #canStep, except a quiet suspend state is OK
try {
return isSuspendedQuiet() || (isSuspended()
&& !(isPerformingEvaluation() || isInvokingMethod())
&& !isStepping()
&& getTopStackFrame() != null
&& !getJavaDebugTarget().isPerformingHotCodeReplace());
} catch (DebugException e) {
return false;
}
}
/**
* @see org.eclipse.jdt.debug.core.IJavaThread#queueRunnable(Runnable)
*/
public void queueRunnable(Runnable evaluation) {
if (fAsyncJob == null) {
fAsyncJob= new ThreadJob(this);
}
fAsyncJob.addRunnable(evaluation);
}
/**
* @see IJavaThread#terminateEvaluation()
*/
public void terminateEvaluation() throws DebugException {
if (canTerminateEvaluation()) {
((ITerminate) fEvaluationRunnable).terminate();
}
}
/**
* @see IJavaThread#canTerminateEvaluation()
*/
public boolean canTerminateEvaluation() {
return fEvaluationRunnable instanceof ITerminate;
}
/**
* Invokes a method on the target, in this thread, and returns the result. Only
* one receiver may be specified - either a class or an object, the other must
* be <code>null</code>. This thread is left suspended after the invocation
* is complete, unless a call is made to <code>abortEvaluation<code> while
* performing a method invocation. In that case, this thread is automatically
* resumed when/if this invocation (eventually) completes.
* <p>
* Method invocations cannot be nested. That is, this method must
* return before another call to this method can be made. This
* method does not return until the invocation is complete.
* Breakpoints can suspend a method invocation, and it is possible
* that an invocation will not complete due to an infinite loop
* or deadlock.
* </p>
* <p>
* Stack frames are preserved during method invocations, unless
* a timeout occurs. Although this thread's state is updated to
* running while performing an evaluation, no debug events are
* fired unless this invocation is interrupted by a breakpoint,
* or the invocation times out.
* </p>
* <p>
* When performing an invocation, the communication timeout with
* the target VM is set to infinite, as the invocation may not
* complete in a timely fashion, if at all. The timeout value
* is reset to its original value when the invocation completes.
* </p>
*
* @param receiverClass the class in the target representing the receiver
* of a static message send, or <code>null</code>
* @param receiverObject the object in the target to be the receiver of
* the message send, or <code>null</code>
* @param method the underlying method to be invoked
* @param args the arguments to invoke the method with (an empty list
* if none)
* @return the result of the method, as an underlying value
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* <li>This thread is not suspended
* (status code <code>IJavaThread.ERR_THREAD_NOT_SUSPENDED</code>)</li>
* <li>This thread is already invoking a method
* (status code <code>IJavaThread.ERR_NESTED_METHOD_INVOCATION</code>)</li>
* <li>This thread is not suspended by a JDI request
* (status code <code>IJavaThread.ERR_INCOMPATIBLE_THREAD_STATE</code>)</li>
* </ul>
*/
protected Value invokeMethod(ClassType receiverClass, ObjectReference receiverObject, Method method, List args, boolean invokeNonvirtual) throws DebugException {
if (receiverClass != null && receiverObject != null) {
throw new IllegalArgumentException(JDIDebugModelMessages.JDIThread_can_only_specify_one_receiver_for_a_method_invocation);
}
Value result= null;
int timeout= getRequestTimeout();
try {
// this is synchronized such that any other operation that
// might be resuming this thread has a chance to complete before
// we determine if it is safe to continue with a method invocation.
// See bugs 6518, 14069
synchronized (this) {
if (!isSuspended()) {
requestFailed(JDIDebugModelMessages.JDIThread_Evaluation_failed___thread_not_suspended, null, IJavaThread.ERR_THREAD_NOT_SUSPENDED);
}
if (isInvokingMethod()) {
requestFailed(JDIDebugModelMessages.JDIThread_Cannot_perform_nested_evaluations, null, IJavaThread.ERR_NESTED_METHOD_INVOCATION);
}
// set the request timeout to be infinite
setRequestTimeout(Integer.MAX_VALUE);
setRunning(true);
setInvokingMethod(true);
}
preserveStackFrames();
int flags= ClassType.INVOKE_SINGLE_THREADED;
if (invokeNonvirtual) {
// Superclass method invocation must be performed nonvirtual.
flags |= ObjectReference.INVOKE_NONVIRTUAL;
}
if (receiverClass == null) {
result= receiverObject.invokeMethod(fThread, method, args, flags);
} else {
result= receiverClass.invokeMethod(fThread, method, args, flags);
}
} catch (InvalidTypeException e) {
invokeFailed(e, timeout);
} catch (ClassNotLoadedException e) {
invokeFailed(e, timeout);
} catch (IncompatibleThreadStateException e) {
invokeFailed(JDIDebugModelMessages.JDIThread_Thread_must_be_suspended_by_step_or_breakpoint_to_perform_method_invocation_1, IJavaThread.ERR_INCOMPATIBLE_THREAD_STATE, e, timeout);
} catch (InvocationException e) {
invokeFailed(e, timeout);
} catch (RuntimeException e) {
invokeFailed(e, timeout);
}
invokeComplete(timeout);
return result;
}
/**
* Invokes a constructor in this thread, creating a new instance of the given
* class, and returns the result as an object reference.
* This thread is left suspended after the invocation
* is complete.
* <p>
* Method invocations cannot be nested. That is, this method must
* return before another call to this method can be made. This
* method does not return until the invocation is complete.
* Breakpoints can suspend a method invocation, and it is possible
* that an invocation will not complete due to an infinite loop
* or deadlock.
* </p>
* <p>
* Stack frames are preserved during method invocations, unless
* a timeout occurs. Although this thread's state is updated to
* running while performing an evaluation, no debug events are
* fired unless this invocation is interrupted by a breakpoint,
* or the invocation times out.
* </p>
* <p>
* When performing an invocation, the communication timeout with
* the target VM is set to infinite, as the invocation may not
* complete in a timely fashion, if at all. The timeout value
* is reset to its original value when the invocation completes.
* </p>
*
* @param receiverClass the class in the target representing the receiver
* of the 'new' message send
* @param constructor the underlying constructor to be invoked
* @param args the arguments to invoke the constructor with (an empty list
* if none)
* @return a new object reference
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected ObjectReference newInstance(ClassType receiverClass, Method constructor, List args) throws DebugException {
if (isInvokingMethod()) {
requestFailed(JDIDebugModelMessages.JDIThread_Cannot_perform_nested_evaluations_2, null);
}
ObjectReference result= null;
int timeout= getRequestTimeout();
try {
// set the request timeout to be infinite
setRequestTimeout(Integer.MAX_VALUE);
setRunning(true);
setInvokingMethod(true);
preserveStackFrames();
result= receiverClass.newInstance(fThread, constructor, args, ClassType.INVOKE_SINGLE_THREADED);
} catch (InvalidTypeException e) {
invokeFailed(e, timeout);
} catch (ClassNotLoadedException e) {
invokeFailed(e, timeout);
} catch (IncompatibleThreadStateException e) {
invokeFailed(e, timeout);
} catch (InvocationException e) {
invokeFailed(e, timeout);
} catch (RuntimeException e) {
invokeFailed(e, timeout);
}
invokeComplete(timeout);
return result;
}
/**
* Called when an invocation fails. Performs cleanup
* and throws an exception.
*
* @param e the exception that caused the failure
* @param restoreTimeout the communication timeout value,
* in milliseconds, that should be reset
* @see #invokeComplete(int)
* @exception DebugException. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void invokeFailed(Throwable e, int restoreTimeout) throws DebugException {
invokeFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_invoking_method, new String[] {e.toString()}), DebugException.TARGET_REQUEST_FAILED, e, restoreTimeout);
}
/**
* Called when an invocation fails. Performs cleanup
* and throws an exception.
*
* @param message error message
* @param code status code
* @param e the exception that caused the failure
* @param restoreTimeout the communication timeout value,
* in milliseconds, that should be reset
* @see #invokeComplete(int)
* @exception DebugException. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void invokeFailed(String message, int code, Throwable e, int restoreTimeout) throws DebugException {
invokeComplete(restoreTimeout);
requestFailed(message, e, code);
}
/**
* Called when a method invocation has returned, successfully
* or not. This method performs cleanup:<ul>
* <li>Resets the state of this thread to suspended</li>
* <li>Restores the communication timeout value</li>
* <li>Computes the new set of stack frames for this thread</code>
* </ul>
*
* @param restoreTimeout the communication timeout value,
* in milliseconds, that should be reset
* @see #invokeMethod(ClassType, ObjectReference, Method, List)
* @see #newInstance(ClassType, Method, List)
*/
protected synchronized void invokeComplete(int restoreTimeout) {
if (!fIsEvaluatingConditionalBreakpoint) {
abortStep();
}
setInvokingMethod(false);
setRunning(false);
setRequestTimeout(restoreTimeout);
// update preserved stack frames
try {
computeStackFrames();
} catch (DebugException e) {
logError(e);
}
}
/**
* Sets whether or not this thread is currently evaluating a conditional
* breakpoint expression. This state is maintained as a workaround to problems
* that can occur related to the interaction of stepping and expression
* evaluation. See bug 81658 for more details.
* @param evaluating
*/
public void setEvaluatingConditionalBreakpoint(boolean evaluating) {
fIsEvaluatingConditionalBreakpoint= evaluating;
}
/**
* @see IThread#getName()
*/
public String getName() throws DebugException {
try {
fPreviousName = fThread.name();
} catch (RuntimeException e) {
// Don't bother reporting the exception when retrieving the name (bug 30785 & bug 33276)
if (e instanceof ObjectCollectedException) {
if (fPreviousName == null) {
fPreviousName= JDIDebugModelMessages.JDIThread_garbage_collected_1;
}
} else if (e instanceof VMDisconnectedException) {
if (fPreviousName == null) {
fPreviousName= JDIDebugModelMessages.JDIThread_42;
}
} else {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_retrieving_thread_name, new String[] {e.toString()}), e);
}
}
return fPreviousName;
}
/**
* @see IThread#getPriority
*/
public int getPriority() throws DebugException {
// to get the priority, we must get the value from the "priority" field
Field p= null;
try {
p= fThread.referenceType().fieldByName("priority"); //$NON-NLS-1$
if (p == null) {
requestFailed(JDIDebugModelMessages.JDIThread_no_priority_field, null);
}
Value v= fThread.getValue(p);
if (v instanceof IntegerValue) {
return ((IntegerValue)v).value();
}
requestFailed(JDIDebugModelMessages.JDIThread_priority_not_an_integer, null);
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_retrieving_thread_priority, new String[] {e.toString()}), e);
}
// execution will not fall through to this line, as
// #targetRequestFailed or #requestFailed will throw
// an exception
return -1;
}
/**
* @see IThread#getTopStackFrame()
*/
public synchronized IStackFrame getTopStackFrame() throws DebugException {
List c= computeStackFrames();
if (c.isEmpty()) {
return null;
}
return (IStackFrame) c.get(0);
}
/**
* A breakpoint has suspended execution of this thread.
* Aborts any step currently in process and fires a
* suspend event.
*
* @param breakpoint the breakpoint that caused the suspend
* @return whether this thread suspended
*/
public synchronized boolean handleSuspendForBreakpoint(JavaBreakpoint breakpoint, boolean queueEvent) {
addCurrentBreakpoint(breakpoint);
setSuspendedQuiet(false);
try {
// update state to suspended but don't actually
// suspend unless a registered listener agrees
if (breakpoint.getSuspendPolicy() == IJavaBreakpoint.SUSPEND_VM) {
((JDIDebugTarget)getDebugTarget()).prepareToSuspendByBreakpoint(breakpoint);
} else {
setRunning(false);
}
// poll listeners
boolean suspend = JDIDebugPlugin.getDefault().fireBreakpointHit(this, breakpoint);
// suspend or resume
if (suspend) {
if (breakpoint.getSuspendPolicy() == IJavaBreakpoint.SUSPEND_VM) {
((JDIDebugTarget)getDebugTarget()).suspendedByBreakpoint(breakpoint, queueEvent);
}
abortStep();
if (queueEvent) {
queueSuspendEvent(DebugEvent.BREAKPOINT);
} else {
fireSuspendEvent(DebugEvent.BREAKPOINT);
}
} else {
if (breakpoint.getSuspendPolicy() == IJavaBreakpoint.SUSPEND_VM) {
((JDIDebugTarget)getDebugTarget()).cancelSuspendByBreakpoint(breakpoint);
} else {
setRunning(true);
// dispose cached stack frames so we re-retrieve on the next breakpoint
preserveStackFrames();
}
}
return suspend;
} catch (CoreException e) {
logError(e);
setRunning(true);
return false;
}
}
public void wonSuspendVote(JavaBreakpoint breakpoint) {
setSuspendedQuiet(false);
try {
setRunning(false);
if (breakpoint.getSuspendPolicy() == IJavaBreakpoint.SUSPEND_VM) {
((JDIDebugTarget)getDebugTarget()).suspendedByBreakpoint(breakpoint, false);
}
} catch (CoreException e) {
logError(e);
}
}
/**
* Updates the state of this thread to suspend for
* the given breakpoint but does not fire notification
* of the suspend. Do no abort the current step as the program
* may be resumed quietly and the step may still finish.
*/
public synchronized boolean handleSuspendForBreakpointQuiet(JavaBreakpoint breakpoint) {
addCurrentBreakpoint(breakpoint);
setSuspendedQuiet(true);
setRunning(false);
return true;
}
/**
* @see IStep#isStepping()
*/
public boolean isStepping() {
return getPendingStepHandler() != null;
}
/**
* @see ISuspendResume#isSuspended()
*/
public boolean isSuspended() {
return !fRunning && !fTerminated;
}
/**
* @see ISuspendResume#isSuspended()
*/
public boolean isSuspendedQuiet() {
return fSuspendedQuiet;
}
/**
* @see IJavaThread#isSystemThread()
*/
public boolean isSystemThread() {
return fIsSystemThread;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.debug.core.IJavaThread#isDaemon()
*/
public boolean isDaemon() throws DebugException {
return fIsDaemon;
}
/**
* @see IJavaThread#getThreadGroupName()
*/
public String getThreadGroupName() throws DebugException {
if (fThreadGroupName == null) {
ThreadGroupReference tgr= getUnderlyingThreadGroup();
// bug# 20370
if (tgr == null) {
return null;
}
try {
fThreadGroupName = tgr.name();
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_retrieving_thread_group_name, new String[] {e.toString()}), e);
// execution will not reach this line, as
// #targetRequestFailed will thrown an exception
return null;
}
}
return fThreadGroupName;
}
/**
* @see ITerminate#isTerminated()
*/
public boolean isTerminated() {
return fTerminated;
}
public synchronized boolean isOutOfSynch() throws DebugException {
if (isSuspended() && ((JDIDebugTarget)getDebugTarget()).hasHCRFailed()) {
List frames= computeStackFrames();
Iterator iter= frames.iterator();
while (iter.hasNext()) {
if (((JDIStackFrame) iter.next()).isOutOfSynch()) {
return true;
}
}
return false;
}
// If the thread is not suspended, there's no way to
// say for certain that it is running out of synch code
return false;
}
public boolean mayBeOutOfSynch() {
if (!isSuspended()) {
return ((JDIDebugTarget)getDebugTarget()).hasHCRFailed();
}
return false;
}
/**
* Sets whether this thread is terminated
*
* @param terminated whether this thread is terminated
*/
protected void setTerminated(boolean terminated) {
fTerminated= terminated;
}
/**
* @see ISuspendResume#resume()
*/
public synchronized void resume() throws DebugException {
if (getDebugTarget().isSuspended()) {
getDebugTarget().resume();
} else {
resumeThread(true);
}
}
/**
* @see ISuspendResume#resume()
*
* Updates the state of this thread to resumed,
* but does not fire notification of the resumption.
*/
public synchronized void resumeQuiet() throws DebugException {
if (isSuspendedQuiet()) {
resumeThread(false);
}
}
/**
* @see ISuspendResume#resume()
*
* Updates the state of this thread, but only fires
* notification to listeners if <code>fireNotification</code>
* is <code>true</code>.
*/
private synchronized void resumeThread(boolean fireNotification) throws DebugException {
if (!isSuspended() || (isPerformingEvaluation() && !isInvokingMethod())) {
return;
}
try {
setRunning(true);
setSuspendedQuiet(false);
if (fireNotification) {
fireResumeEvent(DebugEvent.CLIENT_REQUEST);
}
preserveStackFrames();
fThread.resume();
} catch (VMDisconnectedException e) {
disconnected();
} catch (RuntimeException e) {
setRunning(false);
fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_resuming, new String[] {e.toString()}), e);
}
}
/**
* Sets whether this thread is currently executing.
* When set to <code>true</code>, this thread's current
* breakpoints are cleared.
*
* @param running whether this thread is executing
*/
protected void setRunning(boolean running) {
fRunning = running;
if (running) {
fCurrentBreakpoints.clear();
}
}
protected void setSuspendedQuiet(boolean suspendedQuiet) {
fSuspendedQuiet= suspendedQuiet;
}
/**
* Preserves stack frames to be used on the next suspend event.
* Iterates through all current stack frames, setting their
* state as invalid. This method should be called before this thread
* is resumed, when stack frames are to be re-used when it later
* suspends.
*
* @see computeStackFrames()
*/
protected synchronized void preserveStackFrames() {
fRefreshChildren = true;
Iterator frames = fStackFrames.iterator();
while (frames.hasNext()) {
((JDIStackFrame)frames.next()).setUnderlyingStackFrame(null);
}
}
/**
* Disposes stack frames, to be completely re-computed on
* the next suspend event. This method should be called before
* this thread is resumed when stack frames are not to be re-used
* on the next suspend.
*
* @see computeStackFrames()
*/
protected synchronized void disposeStackFrames() {
fStackFrames.clear();
fRefreshChildren = true;
}
/**
* This method is synchronized, such that the step request
* begins before a background evaluation can be performed.
*
* @see IStep#stepInto()
*/
public synchronized void stepInto() throws DebugException {
if (!canStepInto()) {
return;
}
StepHandler handler = new StepIntoHandler();
handler.step();
}
/**
* This method is synchronized, such that the step request
* begins before a background evaluation can be performed.
*
* @see IStep#stepOver()
*/
public synchronized void stepOver() throws DebugException {
if (!canStepOver()) {
return;
}
StepHandler handler = new StepOverHandler();
handler.step();
}
/**
* This method is synchronized, such that the step request
* begins before a background evaluation can be performed.
*
* @see IStep#stepReturn()
*/
public synchronized void stepReturn() throws DebugException {
if (!canStepReturn()) {
return;
}
StepHandler handler = new StepReturnHandler();
handler.step();
}
protected void setOriginalStepKind(int stepKind) {
fOriginalStepKind = stepKind;
}
protected int getOriginalStepKind() {
return fOriginalStepKind;
}
protected void setOriginalStepLocation(Location location) {
fOriginalStepLocation = location;
}
protected Location getOriginalStepLocation() {
return fOriginalStepLocation;
}
protected void setOriginalStepStackDepth(int depth) {
fOriginalStepStackDepth = depth;
}
protected int getOriginalStepStackDepth() {
return fOriginalStepStackDepth;
}
/**
* In cases where a user-requested step into encounters nothing but filtered code
* (static initializers, synthetic methods, etc.), the default JDI behavior is to
* put the instruction pointer back where it was before the step into. This requires
* a second step to move forward. Since this is confusing to the user, we do an
* extra step into in such situations. This method determines when such an extra
* step into is necessary. It compares the current Location to the original
* Location when the user step into was initiated. It also makes sure the stack depth
* now is the same as when the step was initiated.
*/
protected boolean shouldDoExtraStepInto(Location location) throws DebugException {
if (getOriginalStepKind() != StepRequest.STEP_INTO) {
return false;
}
if (getOriginalStepStackDepth() != getUnderlyingFrameCount()) {
return false;
}
Location origLocation = getOriginalStepLocation();
if (origLocation == null) {
return false;
}
// We cannot simply check if the two Locations are equal using the equals()
// method, since this checks the code index within the method. Even if the
// code indices are different, the line numbers may be the same, in which case
// we need to do the extra step into.
Method origMethod = origLocation.method();
Method currMethod = location.method();
if (!origMethod.equals(currMethod)) {
return false;
}
if (origLocation.lineNumber() != location.lineNumber()) {
return false;
}
return true;
}
/**
* Determines if a user did a step into and stepped through filtered code.
* In this case, do a step return if the user has requested not to
* step thru to an un-filtered location.
*/
protected boolean shouldDoStepReturn() throws DebugException {
if (getOriginalStepKind() == StepRequest.STEP_INTO) {
if ((getOriginalStepStackDepth() + 1) < getUnderlyingFrameCount()) {
return true;
}
}
return false;
}
/**
* @see ISuspendResume#suspend()
*/
public synchronized void suspend() throws DebugException {
try {
// Abort any pending step request
abortStep();
setSuspendedQuiet(false);
fEvaluationInterrupted = isPerformingEvaluation();
suspendUnderlyingThread();
} catch (RuntimeException e) {
setRunning(true);
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_suspending, new String[] {e.toString()}), e);
}
}
/**
* Suspends the underlying thread asynchronously and fires notification when
* the underlying thread is suspended.
*/
protected synchronized void suspendUnderlyingThread() {
if (fIsSuspending) {
return;
}
if (isSuspended()) {
fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
return;
}
fIsSuspending= true;
Thread thread= new Thread(new Runnable() {
public void run() {
try {
fThread.suspend();
int timeout= JDIDebugModel.getPreferences().getInt(JDIDebugModel.PREF_REQUEST_TIMEOUT);
long stop= System.currentTimeMillis() + timeout;
boolean suspended= isUnderlyingThreadSuspended();
while (System.currentTimeMillis() < stop && !suspended) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
suspended= isUnderlyingThreadSuspended();
if (suspended) {
break;
}
}
if (!suspended) {
IStatus status= new Status(IStatus.ERROR, JDIDebugPlugin.getUniqueIdentifier(), SUSPEND_TIMEOUT, MessageFormat.format(JDIDebugModelMessages.JDIThread_suspend_timeout, new String[] {new Integer(timeout).toString()}), null);
IStatusHandler handler= DebugPlugin.getDefault().getStatusHandler(status);
if (handler != null) {
try {
handler.handleStatus(status, JDIThread.this);
} catch (CoreException e) {
}
}
}
setRunning(false);
fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
} catch (RuntimeException exception) {
} finally {
fIsSuspending= false;
}
}
});
thread.setDaemon(true);
thread.start();
}
public boolean isUnderlyingThreadSuspended() {
return fThread.isSuspended();
}
/**
* Notifies this thread that it has been suspended due
* to a VM suspend.
*/
protected synchronized void suspendedByVM() {
setRunning(false);
setSuspendedQuiet(false);
}
/**
* Notifies this thread that is about to be resumed due
* to a VM resume.
*/
protected synchronized void resumedByVM() throws DebugException {
setRunning(true);
preserveStackFrames();
// This method is called *before* the VM is actually resumed.
// To ensure that all threads will fully resume when the VM
// is resumed, make sure the suspend count of each thread
// is no greater than 1. @see Bugs 23328 and 27622
ThreadReference thread= fThread;
while (thread.suspendCount() > 1) {
try {
thread.resume();
} catch (ObjectCollectedException e) {
} catch (VMDisconnectedException e) {
disconnected();
}catch (RuntimeException e) {
setRunning(false);
fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_resuming, new String[] {e.toString()}), e); //
}
}
}
/**
* @see ITerminate#terminate()
*/
public void terminate() throws DebugException {
terminateEvaluation();
getDebugTarget().terminate();
}
/**
* Drops to the given stack frame
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void dropToFrame(IStackFrame frame) throws DebugException {
JDIDebugTarget target= (JDIDebugTarget) getDebugTarget();
if (target.canPopFrames()) {
// JDK 1.4 support
try {
// Pop the drop frame and all frames above it
popFrame(frame);
stepInto();
} catch (RuntimeException exception) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_dropping_to_frame, new String[] {exception.toString()}),exception);
}
} else {
// J9 support
// This block is synchronized, such that the step request
// begins before a background evaluation can be performed.
synchronized (this) {
StepHandler handler = new DropToFrameHandler(frame);
handler.step();
}
}
}
protected void popFrame(IStackFrame frame) throws DebugException {
JDIDebugTarget target= (JDIDebugTarget)getDebugTarget();
if (target.canPopFrames()) {
// JDK 1.4 support
try {
// Pop the frame and all frames above it
StackFrame jdiFrame= null;
int desiredSize= fStackFrames.size() - fStackFrames.indexOf(frame) - 1;
int lastSize= fStackFrames.size() + 1; // Set up to pass the first test
int size= fStackFrames.size();
while (size < lastSize && size > desiredSize) {
// Keep popping frames until the stack stops getting smaller
// or popFrame is gone.
// see Bug 8054
jdiFrame = ((JDIStackFrame) frame).getUnderlyingStackFrame();
preserveStackFrames();
fThread.popFrames(jdiFrame);
lastSize= size;
size= computeStackFrames().size();
}
} catch (IncompatibleThreadStateException exception) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_popping, new String[] {exception.toString()}),exception);
} catch (InvalidStackFrameException exception) {
// InvalidStackFrameException can be thrown when all but the
// deepest frame were popped. Fire a changed notification
// in case this has occured.
fireChangeEvent(DebugEvent.CONTENT);
targetRequestFailed(exception.toString(),exception);
} catch (RuntimeException exception) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_popping, new String[] {exception.toString()}),exception);
}
}
}
/**
* Steps until the specified stack frame is the top frame. Provides
* ability to step over/return in the non-top stack frame.
* This method is synchronized, such that the step request
* begins before a background evaluation can be performed.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected synchronized void stepToFrame(IStackFrame frame) throws DebugException {
if (!canStepReturn()) {
return;
}
StepHandler handler = new StepToFrameHandler(frame);
handler.step();
}
/**
* Aborts the current step, if any.
*/
protected void abortStep() {
StepHandler handler = getPendingStepHandler();
if (handler != null) {
handler.abort();
}
}
/**
* @see IJavaThread#findVariable(String)
*/
public IJavaVariable findVariable(String varName) throws DebugException {
if (isSuspended()) {
try {
IStackFrame[] stackFrames= getStackFrames();
for (int i = 0; i < stackFrames.length; i++) {
IJavaStackFrame sf= (IJavaStackFrame)stackFrames[i];
IJavaVariable var= sf.findVariable(varName);
if (var != null) {
return var;
}
}
} catch (DebugException e) {
// if the thread has since reusmed, return null (no need to report error)
if (e.getStatus().getCode() != IJavaThread.ERR_THREAD_NOT_SUSPENDED) {
throw e;
}
}
}
return null;
}
/**
* Notification this thread has terminated - update state
* and fire a terminate event.
*/
protected void terminated() {
setTerminated(true);
setRunning(false);
fireTerminateEvent();
}
/**
* Returns this thread on the underlying VM which this
* model thread is a proxy to.
*
* @return underlying thread
*/
public ThreadReference getUnderlyingThread() {
return fThread;
}
/**
* Sets the underlying thread that this model object
* is a proxy to.
*
* @param thread underlying thread on target VM
*/
protected void setUnderlyingThread(ThreadReference thread) {
fThread = thread;
}
/**
* Returns this thread's underlying thread group.
*
* @return thread group
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* <li>Retrieving the underlying thread group is not supported
* on the underlying VM</li>
* </ul>
*/
protected ThreadGroupReference getUnderlyingThreadGroup() throws DebugException {
if (fThreadGroup == null) {
try {
fThreadGroup = fThread.threadGroup();
} catch (UnsupportedOperationException e) {
requestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_retrieving_thread_group, new String[] {e.toString()}), e);
// execution will not reach this line, as
// #requestFailed will throw an exception
return null;
} catch (VMDisconnectedException e) {
// ignore disconnect
return null;
} catch (ObjectCollectedException e) {
// ignore object collected
return null;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_retrieving_thread_group, new String[] {e.toString()}), e);
// execution will not reach this line, as
// #targetRequestFailed will throw an exception
return null;
}
}
return fThreadGroup;
}
/**
* @see IJavaThread#isPerformingEvaluation()
*/
public boolean isPerformingEvaluation() {
return fIsPerformingEvaluation;
}
/**
* Returns whether this thread is currently performing
* a method invokation
*/
public boolean isInvokingMethod() {
return fIsInvokingMethod;
}
/**
* Returns whether this thread is currently ignoring
* breakpoints.
*/
public boolean isIgnoringBreakpoints() {
return !fHonorBreakpoints;
}
/**
* Sets whether this thread is currently invoking a method
*
* @param evaluating whether this thread is currently
* invoking a method
*/
protected void setInvokingMethod(boolean invoking) {
fIsInvokingMethod= invoking;
}
/**
* Sets the step handler currently handling a step
* request.
*
* @param handler the current step handler, or <code>null</code>
* if none
*/
protected void setPendingStepHandler(StepHandler handler) {
fStepHandler = handler;
}
/**
* Returns the step handler currently handling a step
* request, or <code>null</code> if none.
*
* @return step handler, or <code>null</code> if none
*/
protected StepHandler getPendingStepHandler() {
return fStepHandler;
}
/**
* Helper class to perform stepping an a thread.
*/
abstract class StepHandler implements IJDIEventListener {
/**
* Request for stepping in the underlying VM
*/
private StepRequest fStepRequest;
/**
* Initiates a step in the underlying VM by creating a step
* request of the appropriate kind (over, into, return),
* and resuming this thread. When a step is initiated it
* is registered with its thread as a pending step. A pending
* step could be cancelled if a breakpoint suspends execution
* during the step.
* <p>
* This thread's state is set to running and stepping, and
* stack frames are invalidated (but preserved to be re-used
* when the step completes). A resume event with a step detail
* is fired for this thread.
* </p>
* Note this method does nothing if this thread has no stack frames.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void step() throws DebugException {
JDIStackFrame top = (JDIStackFrame)getTopStackFrame();
if (top == null) {
return;
}
setOriginalStepKind(getStepKind());
Location location = top.getUnderlyingStackFrame().location();
setOriginalStepLocation(location);
setOriginalStepStackDepth(computeStackFrames().size());
setStepRequest(createStepRequest());
setPendingStepHandler(this);
addJDIEventListener(this, getStepRequest());
setRunning(true);
preserveStackFrames();
fireResumeEvent(getStepDetail());
invokeThread();
}
/**
* Resumes the underlying thread to initiate the step.
* By default the thread is resumed. Step handlers that
* require other actions can override this method.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void invokeThread() throws DebugException {
try {
fThread.resume();
} catch (RuntimeException e) {
stepEnd();
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_stepping, new String[] {e.toString()}), e);
}
}
/**
* Creates and returns a step request specific to this step
* handler. Subclasses must override <code>getStepKind()</code>
* to return the kind of step it implements.
*
* @return step request
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected StepRequest createStepRequest() throws DebugException {
return createStepRequest(getStepKind());
}
/**
* Creates and returns a step request of the specified kind.
*
* @param one of <code>StepRequest.STEP_INTO</code>,
* <code>StepRequest.STEP_OVER</code>, <code>StepRequest.STEP_OUT</code>
* @return step request
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected StepRequest createStepRequest(int kind) throws DebugException {
EventRequestManager manager = getEventRequestManager();
if (manager == null) {
requestFailed(JDIDebugModelMessages.JDIThread_Unable_to_create_step_request___VM_disconnected__1, null);
}
try {
StepRequest request = manager.createStepRequest(fThread, StepRequest.STEP_LINE, kind);
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request.addCountFilter(1);
attachFiltersToStepRequest(request);
request.enable();
return request;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_creating_step_request, new String[] {e.toString()}), e);
}
// this line will never be executed, as the try block
// will either return, or the catch block will throw
// an exception
return null;
}
/**
* Returns the kind of step this handler implements.
*
* @return one of <code>StepRequest.STEP_INTO</code>,
* <code>StepRequest.STEP_OVER</code>, <code>StepRequest.STEP_OUT</code>
*/
protected abstract int getStepKind();
/**
* Returns the detail for this step event.
*
* @return one of <code>DebugEvent.STEP_INTO</code>,
* <code>DebugEvent.STEP_OVER</code>, <code>DebugEvent.STEP_RETURN</code>
*/
protected abstract int getStepDetail();
/**
* Sets the step request created by this handler in
* the underlying VM. Set to <code>null<code> when
* this handler deletes its request.
*
* @param request step request
*/
protected void setStepRequest(StepRequest request) {
fStepRequest = request;
}
/**
* Returns the step request created by this handler in
* the underlying VM.
*
* @return step request
*/
protected StepRequest getStepRequest() {
return fStepRequest;
}
/**
* Deletes this handler's step request from the underlying VM
* and removes this handler as an event listener.
*/
protected void deleteStepRequest() {
removeJDIEventListener(this, getStepRequest());
try {
EventRequestManager manager = getEventRequestManager();
if (manager != null) {
manager.deleteEventRequest(getStepRequest());
}
setStepRequest(null);
} catch (RuntimeException e) {
logError(e);
}
}
/**
* If step filters are currently switched on and the current location is not a filtered
* location, set all active filters on the step request.
*/
protected void attachFiltersToStepRequest(StepRequest request) {
if (applyStepFilters() && isStepFiltersEnabled()) {
Location currentLocation= getOriginalStepLocation();
if (currentLocation == null || !JAVA_STRATUM_CONSTANT.equals(currentLocation.declaringType().defaultStratum())) {
return;
}
// Removed the fix for bug 5587, to address bug 41510
// //check if the user has already stopped in a filtered location
// //is so do not filter @see bug 5587
// ReferenceType type= currentLocation.declaringType();
// String typeName= type.name();
String[] activeFilters = getJavaDebugTarget().getStepFilters();
// for (int i = 0; i < activeFilters.length; i++) {
// StringMatcher matcher = new StringMatcher(activeFilters[i], false, false);
// if (matcher.match(typeName)) {
// return;
// }
// }
if (activeFilters != null) {
for (int i = 0; i < activeFilters.length; i++) {
request.addClassExclusionFilter(activeFilters[i]);
}
}
}
}
/**
* Returns whether this step handler should use step
* filters when creating its step request. By default,
* step filters can be used by any step request.
* Subclasses must override if/when required.
*
* @return whether this step handler should use step
* filters when creating its step request
*/
protected boolean applyStepFilters() {
return true;
}
/**
* Notification the step request has completed.
* If the current location matches one of the user-specified
* step filter criteria (e.g., synthetic methods, static initializers),
* then continue stepping.
*
* @see IJDIEventListener#handleEvent(Event, JDIDebugTarget)
*/
public boolean handleEvent(Event event, JDIDebugTarget target) {
try {
StepEvent stepEvent = (StepEvent) event;
Location currentLocation = stepEvent.location();
if (!getJavaDebugTarget().isStepThruFilters()) {
if (shouldDoStepReturn()) {
deleteStepRequest();
createSecondaryStepRequest(StepRequest.STEP_OUT);
return true;
}
}
// if the ending step location is filtered and we did not start from
// a filtered location, or if we're back where
// we started on a step into, do another step of the same kind
if (locationShouldBeFiltered(currentLocation) || shouldDoExtraStepInto(currentLocation)) {
setRunning(true);
deleteStepRequest();
createSecondaryStepRequest();
return true;
// otherwise, we're done stepping
}
stepEnd();
return false;
} catch (DebugException e) {
logError(e);
stepEnd();
return false;
}
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#wonSuspendVote(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
*/
public void wonSuspendVote(Event event, JDIDebugTarget target) {
// do nothing
}
/**
* Returns <code>true</code> if the StepEvent's Location is a Method that the
* user has indicated (via the step filter preferences) should be
* filtered and the step was not initiated from a filtered location.
* Returns <code>false</code> otherwise.
*/
protected boolean locationShouldBeFiltered(Location location) throws DebugException {
if (applyStepFilters()) {
Location origLocation= getOriginalStepLocation();
if (origLocation != null) {
return !locationIsFiltered(origLocation.method()) && locationIsFiltered(location.method());
}
}
return false;
}
/**
* Returns <code>true</code> if the StepEvent's Location is a Method that the
* user has indicated (via the step filter preferences) should be
* filtered. Returns <code>false</code> otherwise.
*/
protected boolean locationIsFiltered(Method method) {
if (isStepFiltersEnabled()) {
boolean filterStatics = getJavaDebugTarget().isFilterStaticInitializers();
boolean filterSynthetics = getJavaDebugTarget().isFilterSynthetics();
boolean filterConstructors = getJavaDebugTarget().isFilterConstructors();
if (!(filterStatics || filterSynthetics || filterConstructors)) {
return false;
}
if ((filterStatics && method.isStaticInitializer()) ||
(filterSynthetics && method.isSynthetic()) ||
(filterConstructors && method.isConstructor()) ) {
return true;
}
}
return false;
}
/**
* Cleans up when a step completes.<ul>
* <li>Thread state is set to suspended.</li>
* <li>Stepping state is set to false</li>
* <li>Stack frames and variables are incrementally updated</li>
* <li>The step request is deleted and removed as
* and event listener</li>
* <li>A suspend event is fired</li>
* </ul>
*/
protected void stepEnd() {
setRunning(false);
deleteStepRequest();
setPendingStepHandler(null);
queueSuspendEvent(DebugEvent.STEP_END);
}
/**
* Creates another step request in the underlying thread of the
* appropriate kind (over, into, return). This thread will
* be resumed by the event dispatcher as this event handler
* will vote to resume suspended threads. When a step is
* initiated it is registered with its thread as a pending
* step. A pending step could be cancelled if a breakpoint
* suspends execution during the step.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void createSecondaryStepRequest() throws DebugException {
createSecondaryStepRequest(getStepKind());
}
/**
* Creates another step request in the underlying thread of the
* specified kind (over, into, return). This thread will
* be resumed by the event dispatcher as this event handler
* will vote to resume suspended threads. When a step is
* initiated it is registered with its thread as a pending
* step. A pending step could be cancelled if a breakpoint
* suspends execution during the step.
*
* @param one of <code>StepRequest.STEP_INTO</code>,
* <code>StepRequest.STEP_OVER</code>, <code>StepRequest.STEP_OUT</code>
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void createSecondaryStepRequest(int kind) throws DebugException {
setStepRequest(createStepRequest(kind));
setPendingStepHandler(this);
addJDIEventListener(this, getStepRequest());
}
/**
* Aborts this step request if active. The step event
* request is deleted from the underlying VM.
*/
protected void abort() {
if (getStepRequest() != null) {
deleteStepRequest();
setPendingStepHandler(null);
}
}
}
/**
* Handler for step over requests.
*/
class StepOverHandler extends StepHandler {
/**
* @see StepHandler#getStepKind()
*/
protected int getStepKind() {
return StepRequest.STEP_OVER;
}
/**
* @see StepHandler#getStepDetail()
*/
protected int getStepDetail() {
return DebugEvent.STEP_OVER;
}
}
/**
* Handler for step into requests.
*/
class StepIntoHandler extends StepHandler {
/**
* @see StepHandler#getStepKind()
*/
protected int getStepKind() {
return StepRequest.STEP_INTO;
}
/**
* @see StepHandler#getStepDetail()
*/
protected int getStepDetail() {
return DebugEvent.STEP_INTO;
}
}
/**
* Handler for step return requests.
*/
class StepReturnHandler extends StepHandler {
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.debug.core.model.JDIThread.StepHandler#locationShouldBeFiltered(com.sun.jdi.Location)
*/
protected boolean locationShouldBeFiltered(Location location) throws DebugException {
// if still at the same depth, do another step return (see bug 38744)
if (getOriginalStepStackDepth() == getUnderlyingFrameCount()) {
return true;
}
return super.locationShouldBeFiltered(location);
}
/**
* @see StepHandler#getStepKind()
*/
protected int getStepKind() {
return StepRequest.STEP_OUT;
}
/**
* @see StepHandler#getStepDetail()
*/
protected int getStepDetail() {
return DebugEvent.STEP_RETURN;
}
}
/**
* Handler for stepping to a specific stack frame
* (stepping in the non-top stack frame). Step returns
* are performed until a specified stack frame is reached
* or the thread is suspended (explicitly, or by a
* breakpoint).
*/
class StepToFrameHandler extends StepReturnHandler {
/**
* The number of frames that should be left on the stack
*/
private int fRemainingFrames;
/**
* Constructs a step handler to step until the specified
* stack frame is reached.
*
* @param frame the stack frame to step to
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected StepToFrameHandler(IStackFrame frame) throws DebugException {
List frames = computeStackFrames();
setRemainingFrames(frames.size() - frames.indexOf(frame));
}
/**
* Sets the number of frames that should be
* remaining on the stack when done.
*
* @param num number of remaining frames
*/
protected void setRemainingFrames(int num) {
fRemainingFrames = num;
}
/**
* Returns number of frames that should be
* remaining on the stack when done
*
* @return number of frames that should be left
*/
protected int getRemainingFrames() {
return fRemainingFrames;
}
/**
* Notification the step request has completed.
* If in the desired frame, complete the step
* request normally. If not in the desired frame,
* another step request is created and this thread
* is resumed.
*
* @see IJDIEventListener#handleEvent(Event, JDIDebugTarget)
*/
public boolean handleEvent(Event event, JDIDebugTarget target) {
try {
int numFrames = getUnderlyingFrameCount();
// tos should not be null
if (numFrames <= getRemainingFrames()) {
stepEnd();
return false;
}
// reset running state and keep going
setRunning(true);
deleteStepRequest();
createSecondaryStepRequest();
return true;
} catch (DebugException e) {
logError(e);
stepEnd();
return false;
}
}
}
/**
* Handles dropping to a specified frame.
*/
class DropToFrameHandler extends StepReturnHandler {
/**
* The number of frames to drop off the
* stack.
*/
private int fFramesToDrop;
/**
* Constructs a handler to drop to the specified
* stack frame.
*
* @param frame the stack frame to drop to
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected DropToFrameHandler(IStackFrame frame) throws DebugException {
List frames = computeStackFrames();
setFramesToDrop(frames.indexOf(frame));
}
/**
* Sets the number of frames to pop off the stack.
*
* @param num number of frames to pop
*/
protected void setFramesToDrop(int num) {
fFramesToDrop = num;
}
/**
* Returns the number of frames to pop off the stack.
*
* @return remaining number of frames to pop
*/
protected int getFramesToDrop() {
return fFramesToDrop;
}
/**
* To drop a frame or re-enter, the underlying thread is instructed
* to do a return. When the frame count is less than zero, the
* step being performed is a "step return", so a regular invocation
* is performed.
*
* @see StepHandler#invokeThread()
*/
protected void invokeThread() throws DebugException {
if (getFramesToDrop() < 0) {
super.invokeThread();
} else {
try {
org.eclipse.jdi.hcr.ThreadReference hcrThread= (org.eclipse.jdi.hcr.ThreadReference) fThread;
hcrThread.doReturn(null, true);
} catch (RuntimeException e) {
stepEnd();
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_while_popping_stack_frame, new String[] {e.toString()}), e);
}
}
}
/**
* Notification that the pop has completed. If there are
* more frames to pop, keep going, otherwise re-enter the
* top frame. Returns false, as this handler will resume this
* thread with a special invocation (<code>doReturn</code>).
*
* @see IJDIEventListener#handleEvent(Event, JDIDebugTarget)
* @see #invokeThread()
*/
public boolean handleEvent(Event event, JDIDebugTarget target) {
// pop is complete, update number of frames to drop
setFramesToDrop(getFramesToDrop() - 1);
try {
if (getFramesToDrop() >= -1) {
deleteStepRequest();
doSecondaryStep();
} else {
stepEnd();
}
} catch (DebugException e) {
stepEnd();
logError(e);
}
return false;
}
/**
* Pops a secondary frame off the stack, does a re-enter,
* or a step-into.
*
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected void doSecondaryStep() throws DebugException {
setStepRequest(createStepRequest());
setPendingStepHandler(this);
addJDIEventListener(this, getStepRequest());
invokeThread();
}
/**
* Creates and returns a step request. If there
* are no more frames to drop, a re-enter request
* is made. If the re-enter is complete, a step-into
* request is created.
*
* @return step request
* @exception DebugException if this method fails. Reasons include:
* <ul>
* <li>Failure communicating with the VM. The DebugException's
* status code contains the underlying exception responsible for
* the failure.</li>
* </ul>
*/
protected StepRequest createStepRequest() throws DebugException {
EventRequestManager manager = getEventRequestManager();
if (manager == null) {
requestFailed(JDIDebugModelMessages.JDIThread_Unable_to_create_step_request___VM_disconnected__2, null);
}
int num = getFramesToDrop();
if (num > 0) {
return super.createStepRequest();
} else if (num == 0) {
try {
StepRequest request = ((org.eclipse.jdi.hcr.EventRequestManager) manager).createReenterStepRequest(fThread);
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request.addCountFilter(1);
request.enable();
return request;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_creating_step_request, new String[] {e.toString()}), e);
}
} else if (num == -1) {
try {
StepRequest request = manager.createStepRequest(fThread, StepRequest.STEP_LINE, StepRequest.STEP_INTO);
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
request.addCountFilter(1);
request.enable();
return request;
} catch (RuntimeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_creating_step_request, new String[] {e.toString()}), e);
}
}
// this line will never be executed, as the try block
// will either return, or the catch block with throw
// an exception
return null;
}
}
/**
* @see IThread#hasStackFrames()
*/
public boolean hasStackFrames() throws DebugException {
return isSuspended();
}
/**
* @see IAdaptable#getAdapter(Class)
*/
public Object getAdapter(Class adapter) {
if (adapter == IJavaThread.class) {
return this;
}
if (adapter == IJavaStackFrame.class) {
try {
return getTopStackFrame();
} catch (DebugException e) {
// do nothing if not able to get frame
}
}
return super.getAdapter(adapter);
}
/**
* @see org.eclipse.jdt.debug.core.IJavaThread#hasOwnedMonitors()
*/
public boolean hasOwnedMonitors() throws DebugException {
return isSuspended() && getOwnedMonitors().length > 0;
}
/**
* @see org.eclipse.jdt.debug.core.IJavaThread#getOwnedMonitors()
*/
public IJavaObject[] getOwnedMonitors() throws DebugException {
try {
JDIDebugTarget target= (JDIDebugTarget)getDebugTarget();
List ownedMonitors= fThread.ownedMonitors();
IJavaObject[] javaOwnedMonitors= new IJavaObject[ownedMonitors.size()];
Iterator itr= ownedMonitors.iterator();
int i= 0;
while (itr.hasNext()) {
ObjectReference element = (ObjectReference) itr.next();
javaOwnedMonitors[i]= new JDIObjectValue(target, element);
i++;
}
return javaOwnedMonitors;
} catch (IncompatibleThreadStateException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_43, e);
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_44, e);
}
return null;
}
/**
* @see org.eclipse.jdt.debug.core.IJavaThread#getContendedMonitor()
*/
public IJavaObject getContendedMonitor() throws DebugException {
try {
ObjectReference monitor= fThread.currentContendedMonitor();
if (monitor != null) {
return new JDIObjectValue((JDIDebugTarget)getDebugTarget(), monitor);
}
} catch (IncompatibleThreadStateException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_45, e);
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_46, e);
}
return null;
}
/**
* @see org.eclipse.debug.core.model.IFilteredStep#canStepWithFilters()
*/
public boolean canStepWithFilters() {
if (canStepInto()) {
String[] filters = getJavaDebugTarget().getStepFilters();
return filters != null && filters.length > 0;
}
return false;
}
/**
* @see org.eclipse.debug.core.model.IFilteredStep#stepWithFilters()
*/
public void stepWithFilters() throws DebugException {
if (!canStepWithFilters()) {
return;
}
stepInto();
}
/**
* Class which managed the queue of runnable associated with this thread.
*/
static class ThreadJob extends Job {
private Vector fRunnables;
private JDIThread fJDIThread;
public ThreadJob(JDIThread thread) {
super(JDIDebugModelMessages.JDIThread_39);
fJDIThread= thread;
fRunnables= new Vector(5);
setSystem(true);
}
public void addRunnable(Runnable runnable) {
synchronized (fRunnables) {
fRunnables.add(runnable);
}
schedule();
}
public boolean isEmpty() {
return fRunnables.isEmpty();
}
/*
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus run(IProgressMonitor monitor) {
fJDIThread.fRunningAsyncJob= this;
Object[] runnables;
synchronized (fRunnables) {
runnables= fRunnables.toArray();
fRunnables.clear();
}
MultiStatus failed = null;
monitor.beginTask(this.getName(), runnables.length);
int i = 0;
while (i < runnables.length && !fJDIThread.isTerminated() && !monitor.isCanceled()) {
try {
((Runnable) runnables[i]).run();
} catch (Exception e) {
if (failed == null) {
failed = new MultiStatus(JDIDebugPlugin.getUniqueIdentifier(), JDIDebugPlugin.INTERNAL_ERROR, JDIDebugModelMessages.JDIThread_0, null);
}
failed.add(new Status(IStatus.ERROR, JDIDebugPlugin.getUniqueIdentifier(), JDIDebugPlugin.INTERNAL_ERROR, JDIDebugModelMessages.JDIThread_0, e));
}
i++;
monitor.worked(1);
}
fJDIThread.fRunningAsyncJob= null;
monitor.done();
if (failed == null) {
return Status.OK_STATUS;
}
return failed;
}
/*
* @see org.eclipse.core.runtime.jobs.Job#shouldRun()
*/
public boolean shouldRun() {
return !fJDIThread.isTerminated() && !fRunnables.isEmpty();
}
}
/**
* @see org.eclipse.jdt.debug.core.IJavaThread#stop(org.eclipse.jdt.debug.core.IJavaValue)
*/
public void stop(IJavaObject exception) throws DebugException {
try {
fThread.stop(((JDIObjectValue)exception).getUnderlyingObject());
} catch (InvalidTypeException e) {
targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIThread_exception_stoping_thread, new String[] {e.toString()}), e);
}
}
/* (non-Javadoc)
* @see org.eclipse.jdt.debug.core.IJavaThread#getThreadGroup()
*/
public IJavaThreadGroup getThreadGroup() throws DebugException {
ThreadGroupReference group = getUnderlyingThreadGroup();
if (group != null) {
return getJavaDebugTarget().findThreadGroup(group);
}
return null;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.debug.core.IJavaThread#getFrameCount()
*/
public int getFrameCount() throws DebugException {
return getUnderlyingFrameCount();
}
protected void forceReturn(IJavaValue value) throws DebugException {
if (!isSuspended()) {
return;
}
try {
fThread.forceEarlyReturn(((JDIValue)value).getUnderlyingValue());
stepReturn();
} catch (VMDisconnectedException e) {
disconnected();
} catch (InvalidTypeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_48, e);
} catch (ClassNotLoadedException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_48, e);
} catch (IncompatibleThreadStateException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_48, e);
} catch (UnsupportedOperationException e) {
requestFailed(JDIDebugModelMessages.JDIThread_48, e);
} catch (RuntimeException e) {
targetRequestFailed(JDIDebugModelMessages.JDIThread_48, e);
}
}
/**
* Implementation of a scheduling rule for this thread, which defines how it should behave
* when a request for content job tries to run while the thread is evaluating
*
* @since 3.3.0
*/
class SerialPerObjectRule implements ISchedulingRule {
private Object fObject = null;
public SerialPerObjectRule(Object lock) {
fObject = lock;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule)
*/
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.ISchedulingRule#isConflicting(org.eclipse.core.runtime.jobs.ISchedulingRule)
*/
public boolean isConflicting(ISchedulingRule rule) {
if (rule instanceof SerialPerObjectRule) {
SerialPerObjectRule vup = (SerialPerObjectRule) rule;
return fObject == vup.fObject;
}
return false;
}
}
/**
* returns the scheduling rule for getting content while evaluations are running
* @return the <code>ISchedulingRule</code> for this thread
*
* @since 3.3.0
*/
public ISchedulingRule getThreadRule() {
return new SerialPerObjectRule(this);
}
}