/*******************************************************************************
 * Copyright (c) 2000, 2022 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Microsoft Corporation - supports virtual threads
 *******************************************************************************/
package org.eclipse.jdi.internal;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jdi.OpaqueFrameException;
import org.eclipse.jdi.internal.jdwp.JdwpCommandPacket;
import org.eclipse.jdi.internal.jdwp.JdwpID;
import org.eclipse.jdi.internal.jdwp.JdwpReplyPacket;
import org.eclipse.jdi.internal.jdwp.JdwpThreadID;
import org.eclipse.osgi.util.NLS;

import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.InternalException;
import com.sun.jdi.InvalidStackFrameException;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.NativeMethodException;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.StackFrame;
import com.sun.jdi.ThreadGroupReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMCannotBeModifiedException;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.Value;

/**
 * This class implements the corresponding interfaces declared by the JDI
 * specification. See the com.sun.jdi package for more information.
 *
 */
public class ThreadReferenceImpl extends ObjectReferenceImpl implements	ThreadReference, org.eclipse.jdi.hcr.ThreadReference {
	/** ThreadStatus Constants. */
	public static final int JDWP_THREAD_STATUS_ZOMBIE = 0;
	public static final int JDWP_THREAD_STATUS_RUNNING = 1;
	public static final int JDWP_THREAD_STATUS_SLEEPING = 2;
	public static final int JDWP_THREAD_STATUS_MONITOR = 3;
	public static final int JDWP_THREAD_STATUS_WAIT = 4;

	/** SuspendStatus Constants. */
	public static final int SUSPEND_STATUS_SUSPENDED = 0x01;

	/** Mapping of command codes to strings. */
	private static Map<Integer, String> fgThreadStatusMap = null;

	/** Map with Strings for flag bits. */
	private static String[] fgSuspendStatusStrings = null;

	/** JDWP Tag. */
	protected static final byte tag = JdwpID.THREAD_TAG;

	/** Is thread currently at a breakpoint? */
	private boolean fIsAtBreakpoint = false;

	/**
	 * The cached thread group. A thread's thread group cannot be changed.
	 */
	private ThreadGroupReferenceImpl fThreadGroup = null;

	// Whether a thread is a virtual thread or not is cached
	private volatile boolean isVirtual;
	private volatile boolean isVirtualCached;

	/**
	 * Creates new ThreadReferenceImpl.
	 */
	public ThreadReferenceImpl(VirtualMachineImpl vmImpl, JdwpThreadID threadID) {
		super("ThreadReference", vmImpl, threadID); //$NON-NLS-1$
	}

	/**
	 * Sets at breakpoint flag.
	 */
	public void setIsAtBreakpoint() {
		fIsAtBreakpoint = true;
	}

	/**
	 * Reset flags that can be set when event occurs.
	 */
	public void resetEventFlags() {
		fIsAtBreakpoint = false;
	}

	/**
	 * @returns Value tag.
	 */
	@Override
	public byte getTag() {
		return tag;
	}

	/**
	 * @returns Returns an ObjectReference for the monitor, if any, for which
	 *          this thread is currently waiting.
	 */
	@Override
	public ObjectReference currentContendedMonitor()
			throws IncompatibleThreadStateException {
		if (!virtualMachine().canGetCurrentContendedMonitor()) {
			throw new UnsupportedOperationException();
		}
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_CURRENT_CONTENDED_MONITOR, this);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			case JdwpReplyPacket.THREAD_NOT_SUSPENDED:
				throw new IncompatibleThreadStateException(
						JDIMessages.ThreadReferenceImpl_Thread_was_not_suspended_1);
			}
			defaultReplyErrorHandler(replyPacket.errorCode());

			DataInputStream replyData = replyPacket.dataInStream();
			ObjectReference result = ObjectReferenceImpl.readObjectRefWithTag(
					this, replyData);
			return result;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @see com.sun.jdi.ThreadReference#forceEarlyReturn(com.sun.jdi.Value)
	 * @since 3.3
	 */
	@Override
	public void forceEarlyReturn(Value value) throws InvalidTypeException,
			ClassNotLoadedException, IncompatibleThreadStateException {
		if (!virtualMachineImpl().canBeModified()) {
			throw new VMCannotBeModifiedException(
					JDIMessages.ThreadReferenceImpl_vm_read_only);
		}
		initJdwpRequest();
		ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
		DataOutputStream dataOutStream = new DataOutputStream(byteOutStream);
		try {
			write(this, dataOutStream);
			if (value != null) {
				((ValueImpl) value).writeWithTag((ValueImpl) value,
						dataOutStream);
			} else {
				ValueImpl.writeNullWithTag(this, dataOutStream);
			}
			JdwpReplyPacket reply = requestVM(
					JdwpCommandPacket.TR_FORCE_EARLY_RETURN, byteOutStream);
			switch (reply.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException(
						JDIMessages.ThreadReferenceImpl_thread_object_invalid);
			case JdwpReplyPacket.INVALID_OBJECT:
				throw new ClassNotLoadedException(
						JDIMessages.ThreadReferenceImpl_thread_or_value_unknown);
			case JdwpReplyPacket.THREAD_NOT_SUSPENDED:
			case JdwpReplyPacket.THREAD_NOT_ALIVE:
				throw new IncompatibleThreadStateException(
						JDIMessages.ThreadReferenceImpl_thread_not_suspended);
			case JdwpReplyPacket.NOT_IMPLEMENTED:
				throw new UnsupportedOperationException(
						JDIMessages.ThreadReferenceImpl_no_force_early_return_on_threads);
			case JdwpReplyPacket.OPAQUE_FRAME:
				if (isVirtual()) {
					throw new OpaqueFrameException();
				}
				throw new NativeMethodException(JDIMessages.ThreadReferenceImpl_thread_cannot_force_native_method);
			case JdwpReplyPacket.NO_MORE_FRAMES:
				throw new InvalidStackFrameException(
						JDIMessages.ThreadReferenceImpl_thread_no_stackframes);
			case JdwpReplyPacket.TYPE_MISMATCH:
				throw new InvalidTypeException(
						JDIMessages.ThreadReferenceImpl_incapatible_return_type);
			case JdwpReplyPacket.VM_DEAD:
				throw new VMDisconnectedException(JDIMessages.vm_dead);
			}
			defaultReplyErrorHandler(reply.errorCode());
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @returns Returns the StackFrame at the given index in the thread's
	 *          current call stack.
	 */
	@Override
	public StackFrame frame(int index) throws IncompatibleThreadStateException {
		return frames(index, 1).get(0);
	}

	/**
	 * @see com.sun.jdi.ThreadReference#frameCount()
	 */
	@Override
	public int frameCount() throws IncompatibleThreadStateException {
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_FRAME_COUNT, this);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			case JdwpReplyPacket.THREAD_NOT_SUSPENDED:
				throw new IncompatibleThreadStateException(
						JDIMessages.ThreadReferenceImpl_Thread_was_not_suspended_1);
			}
			defaultReplyErrorHandler(replyPacket.errorCode());

			DataInputStream replyData = replyPacket.dataInStream();
			int result = readInt("frame count", replyData); //$NON-NLS-1$
			return result;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return 0;
		} finally {
			handledJdwpRequest();
		}
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.ThreadReference#frames()
	 */
	@Override
	public List<StackFrame> frames() throws IncompatibleThreadStateException {
		return frames(0, -1);
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.ThreadReference#frames(int, int)
	 */
	@Override
	public List<StackFrame> frames(int start, int length) throws IndexOutOfBoundsException,
			IncompatibleThreadStateException {
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			write(this, outData);
			writeInt(start, "start", outData); //$NON-NLS-1$
			writeInt(length, "length", outData); //$NON-NLS-1$

			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_FRAMES, outBytes);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			case JdwpReplyPacket.THREAD_NOT_SUSPENDED:
				throw new IncompatibleThreadStateException(
						JDIMessages.ThreadReferenceImpl_Thread_was_not_suspended_1);
			case JdwpReplyPacket.INVALID_INDEX:
				throw new IndexOutOfBoundsException(
						JDIMessages.ThreadReferenceImpl_Invalid_index_of_stack_frames_given_4);
			}
			defaultReplyErrorHandler(replyPacket.errorCode());

			DataInputStream replyData = replyPacket.dataInStream();
			int nrOfElements = readInt("elements", replyData); //$NON-NLS-1$
			List<StackFrame> frames = new ArrayList<>(nrOfElements);
			for (int i = 0; i < nrOfElements; i++) {
				StackFrameImpl frame = StackFrameImpl.readWithLocation(this,
						this, replyData);
				if (frame == null) {
					continue;
				}
				frames.add(frame);
			}
			return frames;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.ThreadReference#interrupt()
	 */
	@Override
	public void interrupt() {
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			requestVM(JdwpCommandPacket.TR_INTERRUPT, this);
		} finally {
			handledJdwpRequest();
		}
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.ThreadReference#isAtBreakpoint()
	 */
	@Override
	public boolean isAtBreakpoint() {
		return isSuspended() && fIsAtBreakpoint;
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.ThreadReference#isSuspended()
	 */
	@Override
	public boolean isSuspended() {
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_STATUS, this);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			}
			defaultReplyErrorHandler(replyPacket.errorCode());
			DataInputStream replyData = replyPacket.dataInStream();
			// remove the thread status reply
			readInt("thread status", threadStatusMap(), replyData); //$NON-NLS-1$
			int suspendStatus = readInt(
					"suspend status", suspendStatusStrings(), replyData); //$NON-NLS-1$
			boolean result = suspendStatus == SUSPEND_STATUS_SUSPENDED;
			return result;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return false;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * isVirtual is a preview API of the Java platform. Programs can only use isVirtual when preview features are enabled. Preview features may be
	 * removed in a future release, or upgraded to permanent features of the Java platform.
	 *
	 * @return true if the thread is a virtual thread
	 * @since 3.20
	 */
	public boolean isVirtual() {
		if (isVirtualCached) {
			return isVirtual;
		}
		boolean result = false;
		boolean supportsVirtualThreads = virtualMachineImpl().mayCreateVirtualThreads();
		if (supportsVirtualThreads) {
			initJdwpRequest();
			try {
				JdwpReplyPacket replyPacket = requestVM(JdwpCommandPacket.TR_IS_VIRTUAL, this);
				switch (replyPacket.errorCode()) {
					case JdwpReplyPacket.INVALID_THREAD:
						throw new ObjectCollectedException();
				}
				defaultReplyErrorHandler(replyPacket.errorCode());
				DataInputStream replyData = replyPacket.dataInStream();
				result = readBoolean("isVirtual", replyData); //$NON-NLS-1$
			} catch (IOException e) {
				defaultIOExceptionHandler(e);
				return false;
			} finally {
				handledJdwpRequest();
			}
		}

		isVirtual = result;
		isVirtualCached = true;
		return result;
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.ThreadReference#name()
	 */
	@Override
	public String name() {
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(JdwpCommandPacket.TR_NAME,
					this);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			}
			defaultReplyErrorHandler(replyPacket.errorCode());
			DataInputStream replyData = replyPacket.dataInStream();
			return readString("name", replyData); //$NON-NLS-1$
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.ThreadReference#ownedMonitors()
	 */
	@Override
	public List<ObjectReference> ownedMonitors() throws IncompatibleThreadStateException {
		if (!virtualMachine().canGetOwnedMonitorInfo()) {
			throw new UnsupportedOperationException();
		}
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_OWNED_MONITORS, this);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			case JdwpReplyPacket.THREAD_NOT_SUSPENDED:
				throw new IncompatibleThreadStateException(
						JDIMessages.ThreadReferenceImpl_Thread_was_not_suspended_5);
			}
			defaultReplyErrorHandler(replyPacket.errorCode());
			DataInputStream replyData = replyPacket.dataInStream();

			int nrOfMonitors = readInt("nr of monitors", replyData); //$NON-NLS-1$
			List<ObjectReference> result = new ArrayList<>(nrOfMonitors);
			for (int i = 0; i < nrOfMonitors; i++) {
				result.add(ObjectReferenceImpl.readObjectRefWithTag(this,
						replyData));
			}
			return result;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/* (non-Javadoc)
	 * @see com.sun.jdi.ThreadReference#ownedMonitorsAndFrames()
	 */
	@Override
	public List<com.sun.jdi.MonitorInfo> ownedMonitorsAndFrames()
			throws IncompatibleThreadStateException {
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_OWNED_MONITOR_STACK_DEPTH, this);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
			case JdwpReplyPacket.INVALID_OBJECT:
				throw new ObjectCollectedException(
						JDIMessages.ThreadReferenceImpl_thread_object_invalid);
			case JdwpReplyPacket.THREAD_NOT_SUSPENDED:
				throw new IncompatibleThreadStateException(
						JDIMessages.ThreadReferenceImpl_Thread_was_not_suspended_5);
			case JdwpReplyPacket.NOT_IMPLEMENTED:
				throw new UnsupportedOperationException(
						JDIMessages.ThreadReferenceImpl_no_force_early_return_on_threads);
			case JdwpReplyPacket.VM_DEAD:
				throw new VMDisconnectedException(JDIMessages.vm_dead);
			}
			defaultReplyErrorHandler(replyPacket.errorCode());
			DataInputStream replyData = replyPacket.dataInStream();

			int owned = readInt("owned monitors", replyData); //$NON-NLS-1$
			List<com.sun.jdi.MonitorInfo> result = new ArrayList<>(owned);
			ObjectReference monitor = null;
			int depth = -1;
			for (int i = 0; i < owned; i++) {
				monitor = ObjectReferenceImpl.readObjectRefWithTag(this,
						replyData);
				depth = readInt("stack depth", replyData); //$NON-NLS-1$
				result.add(new MonitorInfoImpl(this, depth, monitor, virtualMachineImpl()));
			}
			return result;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * Resumes this thread.
	 *
	 * @see com.sun.jdi.ThreadReference#resume()
	 */
	@Override
	public void resume() {
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_RESUME, this);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			}
			defaultReplyErrorHandler(replyPacket.errorCode());
			resetEventFlags();
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @return Returns the thread's status.
	 */
	@Override
	public int status() {
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_STATUS, this);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.ABSENT_INFORMATION:
				return THREAD_STATUS_UNKNOWN;
			case JdwpReplyPacket.INVALID_THREAD:
				return THREAD_STATUS_NOT_STARTED;
			}
			defaultReplyErrorHandler(replyPacket.errorCode());
			DataInputStream replyData = replyPacket.dataInStream();
			int threadStatus = readInt(
					"thread status", threadStatusMap(), replyData); //$NON-NLS-1$
			readInt("suspend status", suspendStatusStrings(), replyData); //$NON-NLS-1$
			switch (threadStatus) {
			case JDWP_THREAD_STATUS_ZOMBIE:
				return THREAD_STATUS_ZOMBIE;
			case JDWP_THREAD_STATUS_RUNNING:
				return THREAD_STATUS_RUNNING;
			case JDWP_THREAD_STATUS_SLEEPING:
				return THREAD_STATUS_SLEEPING;
			case JDWP_THREAD_STATUS_MONITOR:
				return THREAD_STATUS_MONITOR;
			case JDWP_THREAD_STATUS_WAIT:
				return THREAD_STATUS_WAIT;
			case -1: // see bug 30816
				return THREAD_STATUS_UNKNOWN;
			}
			throw new InternalException(
					JDIMessages.ThreadReferenceImpl_Unknown_thread_status_received___6
							+ threadStatus);
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return 0;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * Stops this thread with an asynchronous exception.
	 *
	 * @see com.sun.jdi.ThreadReference#stop(com.sun.jdi.ObjectReference)
	 */
	@Override
	public void stop(ObjectReference throwable) throws InvalidTypeException {
		checkVM(throwable);
		ObjectReferenceImpl throwableImpl = (ObjectReferenceImpl) throwable;

		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			write(this, outData);
			throwableImpl.write(this, outData);

			JdwpReplyPacket replyPacket = requestVM(JdwpCommandPacket.TR_STOP,
					outBytes);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			case JdwpReplyPacket.INVALID_CLASS:
				throw new InvalidTypeException(
						JDIMessages.ThreadReferenceImpl_Stop_argument_not_an_instance_of_java_lang_Throwable_in_the_target_VM_7);
			}
			defaultReplyErrorHandler(replyPacket.errorCode());
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * Suspends this thread.
	 *
	 * @see com.sun.jdi.ThreadReference#suspend()
	 */
	@Override
	public void suspend() {
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_SUSPEND, this);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			}
			defaultReplyErrorHandler(replyPacket.errorCode());
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @return Returns the number of pending suspends for this thread.
	 */
	@Override
	public int suspendCount() {
		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_SUSPEND_COUNT, this);
			defaultReplyErrorHandler(replyPacket.errorCode());
			DataInputStream replyData = replyPacket.dataInStream();
			int result = readInt("suspend count", replyData); //$NON-NLS-1$
			return result;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return 0;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @return Returns this thread's thread group.
	 */
	@Override
	public ThreadGroupReference threadGroup() {
		if (fThreadGroup != null) {
			return fThreadGroup;
		}
		initJdwpRequest();
		try {
			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.TR_THREAD_GROUP, this);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			}
			defaultReplyErrorHandler(replyPacket.errorCode());
			DataInputStream replyData = replyPacket.dataInStream();
			fThreadGroup = ThreadGroupReferenceImpl.read(this, replyData);
			return fThreadGroup;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return null;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * Simulate the execution of a return instruction instead of executing the
	 * next byte code in a method.
	 *
	 * @return Returns whether any finally or synchronized blocks are enclosing
	 *         the current instruction.
	 */
	@Override
	public boolean doReturn(Value returnValue,
			boolean triggerFinallyAndSynchronized)
			throws org.eclipse.jdi.hcr.OperationRefusedException {
		virtualMachineImpl().checkHCRSupported();
		ValueImpl valueImpl;
		if (returnValue != null) { // null is used if no value is returned.
			checkVM(returnValue);
			valueImpl = (ValueImpl) returnValue;
		} else {
			try {
				TypeImpl returnType = (TypeImpl) frame(0).location().method()
						.returnType();
				valueImpl = (ValueImpl) returnType.createNullValue();
			} catch (IncompatibleThreadStateException e) {
				throw new org.eclipse.jdi.hcr.OperationRefusedException(
						e.toString());
			} catch (ClassNotLoadedException e) {
				throw new org.eclipse.jdi.hcr.OperationRefusedException(
						e.toString());
			}
		}

		// Note that this information should not be cached.
		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			write(this, outData);
			valueImpl.writeWithTag(this, outData);
			writeBoolean(triggerFinallyAndSynchronized,
					"trigger finaly+sync", outData); //$NON-NLS-1$

			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.HCR_DO_RETURN, outBytes);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.INVALID_THREAD:
				throw new ObjectCollectedException();
			}
			defaultReplyErrorHandler(replyPacket.errorCode());

			DataInputStream replyData = replyPacket.dataInStream();
			boolean result = readBoolean("is enclosed", replyData); //$NON-NLS-1$
			return result;
		} catch (IOException e) {
			defaultIOExceptionHandler(e);
			return false;
		} finally {
			handledJdwpRequest();
		}
	}

	/**
	 * @return Returns description of Mirror object.
	 */
	@Override
	public String toString() {
		try {
			return NLS.bind(JDIMessages.ThreadReferenceImpl_8,
					new String[] { type().toString(), name(),
							getObjectID().toString() });
		} catch (ObjectCollectedException e) {
			return JDIMessages.ThreadReferenceImpl__Garbage_Collected__ThreadReference__9
					+ idString();
		} catch (Exception e) {
			return fDescription;
		}
	}

	/**
	 * @return Reads JDWP representation and returns new instance.
	 */
	public static ThreadReferenceImpl read(MirrorImpl target, DataInputStream in)
			throws IOException {
		VirtualMachineImpl vmImpl = target.virtualMachineImpl();
		JdwpThreadID ID = new JdwpThreadID(vmImpl);
		ID.read(in);
		if (target.fVerboseWriter != null)
		 {
			target.fVerboseWriter.println("threadReference", ID.value()); //$NON-NLS-1$
		}

		if (ID.isNull()) {
			return null;
		}

		ThreadReferenceImpl mirror = (ThreadReferenceImpl) vmImpl
				.getCachedMirror(ID);
		if (mirror == null) {
			mirror = new ThreadReferenceImpl(vmImpl, ID);
			vmImpl.addCachedMirror(mirror);
		}
		return mirror;
	}

	/**
	 * Retrieves constant mappings.
	 */
	public static void getConstantMaps() {
		if (fgThreadStatusMap != null) {
			return;
		}

		Field[] fields = ThreadReferenceImpl.class.getDeclaredFields();
		fgThreadStatusMap = new HashMap<>();
		fgSuspendStatusStrings = new String[32]; // Int

		for (Field field : fields) {
			if ((field.getModifiers() & Modifier.PUBLIC) == 0
					|| (field.getModifiers() & Modifier.STATIC) == 0
					|| (field.getModifiers() & Modifier.FINAL) == 0) {
				continue;
			}

			try {
				String name = field.getName();
				int value = field.getInt(null);
				Integer intValue = Integer.valueOf(value);

				if (name.startsWith("JDWP_THREAD_STATUS_")) { //$NON-NLS-1$
					name = name.substring(19);
					fgThreadStatusMap.put(intValue, name);
				} else if (name.startsWith("SUSPEND_STATUS_")) { //$NON-NLS-1$
					name = name.substring(15);
					for (int j = 0; j < fgSuspendStatusStrings.length; j++) {
						if ((1 << j & value) != 0) {
							fgSuspendStatusStrings[j] = name;
							break;
						}
					}
				}
			} catch (IllegalAccessException e) {
				// Will not occur for own class.
			} catch (IllegalArgumentException e) {
				// Should not occur.
				// We should take care that all public static final constants
				// in this class are numbers that are convertible to int.
			}
		}
	}

	/**
	 * @return Returns a map with string representations of tags.
	 */
	public static Map<Integer, String> threadStatusMap() {
		getConstantMaps();
		return fgThreadStatusMap;
	}

	/**
	 * @return Returns a map with string representations of tags.
	 */
	public static String[] suspendStatusStrings() {
		getConstantMaps();
		return fgSuspendStatusStrings;
	}

	/**
	 * @see ThreadReference#popFrames(StackFrame)
	 */
	@Override
	public void popFrames(StackFrame frameToPop)
			throws IncompatibleThreadStateException {
		if (!isSuspended()) {
			throw new IncompatibleThreadStateException();
		}
		if (!virtualMachineImpl().canPopFrames()) {
			throw new UnsupportedOperationException();
		}

		StackFrameImpl frame = (StackFrameImpl) frameToPop;

		initJdwpRequest();
		try {
			ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
			DataOutputStream outData = new DataOutputStream(outBytes);
			frame.writeWithThread(frame, outData);

			JdwpReplyPacket replyPacket = requestVM(
					JdwpCommandPacket.SF_POP_FRAME, outBytes);
			switch (replyPacket.errorCode()) {
			case JdwpReplyPacket.OPAQUE_FRAME:
				if (isVirtual()) {
					throw new OpaqueFrameException();
				}
				throw new NativeMethodException();
			case JdwpReplyPacket.INVALID_THREAD:
				throw new InvalidStackFrameException();
			case JdwpReplyPacket.INVALID_FRAMEID:
				throw new InvalidStackFrameException(
						JDIMessages.ThreadReferenceImpl_Unable_to_pop_the_requested_stack_frame_from_the_call_stack__Reasons_include__The_frame_id_was_invalid__The_thread_was_resumed__10);
			case JdwpReplyPacket.THREAD_NOT_SUSPENDED:
				throw new IncompatibleThreadStateException(
						JDIMessages.ThreadReferenceImpl_Unable_to_pop_the_requested_stack_frame__The_requested_stack_frame_is_not_suspended_11);
			case JdwpReplyPacket.NO_MORE_FRAMES:
				throw new InvalidStackFrameException(
						JDIMessages.ThreadReferenceImpl_Unable_to_pop_the_requested_stack_frame_from_the_call_stack__Reasons_include__The_requested_frame_was_the_last_frame_on_the_call_stack__The_requested_frame_was_the_last_frame_above_a_native_frame__12);
			default:
				defaultReplyErrorHandler(replyPacket.errorCode());
			}
		} catch (IOException ioe) {
			defaultIOExceptionHandler(ioe);
		} finally {
			handledJdwpRequest();
		}
	}

}
