/*
 * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package nsk.share.jdwp;

import nsk.share.*;
import nsk.share.jpda.*;

import java.util.*;
import java.io.*;

/**
 * This class is used to interact with debugee VM using JDWP features.
 * <p>
 * This class is an mirror of debugee VM that is constructed by
 * <code>Binder</code> and uses <code>Transport</code> object
 * to interact with debugee VM.
 * <p>
 * In addition to the general abities to control of debugee VM process,
 * provided by the base class <code>DebugeeProcess</code>, this class
 * adds some service methods that uses JDWP protocol to simplify interaction
 * with debugee VM (such as finding classes, setting breakpoints,
 * handling events, and so on.).
 *
 * @see Binder
 * @see Transport
 * @see DebugeeProcess
 */
public class Debugee extends DebugeeProcess {

    /** Binder that creates this debugee. */
    protected Binder binder = null;

    protected LinkedList<EventPacket> eventQueue = new LinkedList<EventPacket>();

    protected Transport transport = null;

    /** Make new <code>Debugee</code> object for the given binder. */
    protected Debugee (Binder binder) {
        super(binder);
        this.argumentHandler = binder.getArgumentHandler();
        this.binder = binder;
        prefix = "Debugee> ";
    }

    public void launch(String[] args) throws IOException {
        String cmdLine = ArgumentHandler.joinArguments(args, "\"");
        display("Starting java process:\n" + cmdLine);
        process = binder.launchProcess(args);
    }

    /** Return <code>Binder</code> of the debugee object. */
    public Binder getBinder() {
        return binder;
    }

    /** Return <code>Transport</code> of the debugee object. */
    public Transport getTransport() {
        return transport;
    }

    /**
     * Prepare transport object for establishing connection.
     * This may change connection options in <code>argumentHandler</code>.
     *
     * @return specific address string if listening has started or null otherwise
     */
    public String prepareTransport(ArgumentHandler argumentHandler) {
        String address = null;
        try {
            if (argumentHandler.isSocketTransport()) {
                SocketTransport socket_transport = new SocketTransport(log);
                if (argumentHandler.isListeningConnector()) {
                    int port = 0;
                    if (argumentHandler.isTransportAddressDynamic()) {
                        port = socket_transport.bind(0);
//                        argumentHandler.setTransportPortNumber(port);
                    } else {
                        port = argumentHandler.getTransportPortNumber();
                        socket_transport.bind(port);
                    }
                    address = argumentHandler.getTestHost() + ":" + port;
                }
                transport = socket_transport;
/*
            } else if (argumentHandler.isShmemTransport()) {
                ShmemTransport shmem_transport = new ShmemTransport(log);
                if (argumentHandler.isListeningConnector()) {
                    String sharedName = agrHandler.getTransportSharedName();
                    shmem_transport.bind(sharedName);
                    address = sharedName;
                }
                transport = shmem_transport;
 */
            } else {
               throw new TestBug("Unexpected transport type: "
                               + argumentHandler.getTransportType());
            }

        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught IOException while preparing for JDWP transport connection:\n\t"
                    + e);
        }

        return address;
    }

    /**
     * Establish connection to debugee VM.
     */
    public Transport connect() {
        if (transport == null) {
            throw new Failure("Attemt to establish JDWP connection for not prepared transport");
        }

        try {
            if (argumentHandler.isSocketTransport()) {
                display("Establishing JDWP socket connection");
                SocketTransport socket_transport = (SocketTransport)transport;
                int transportPort = argumentHandler.getTransportPortNumber();
                if (argumentHandler.isAttachingConnector()) {
                    String debugeeHost = argumentHandler.getDebugeeHost();
                    display("Attaching to debugee: " + debugeeHost + ":" + transportPort);
                    socket_transport.attach(debugeeHost, transportPort);
                } else if (argumentHandler.isListeningConnector()) {
                    display("Listening from debugee");
                    socket_transport.accept();
                } else {
                    throw new TestBug("Unexpected connector type: "
                                    + argumentHandler.getConnectorType());
                }
/*
            } else if (argumentHandler.isShmemTransport()) {
                display("Establishing JDWP shared-memory connection");
                ShmemTransport shmem_transport = (ShmemTransport)transport;
                String sharedName = argumentHandler.getTransportSharedName();
                if (argumentHandler.isAttachingConnector()) {
                    display("Attaching to debugee: " + sharedName);
                    shmem_transport.attach(sharedName);
                } else if (argumentHandler.isListeningConnector()) {
                    display("Listening from debugee");
                    shmem_transport.accept();
                } else {
                    throw new TestBug("Unexpected connector type: "
                                    + argumentHandler.getConnectorType());
                }
 */
            } else {
               throw new TestBug("Unexpected transport type: "
                               + argumentHandler.getTransportType());
            }

            transport.handshake();

        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught IOException while establishing JDWP transport connection:\n\t"
                    + e);
        }
        return transport;
    }

    // --------------------------------------------------- //

   /**
    * Waits for VM_INIT event from debugee VM.
    */
    public void waitForVMInit() {
        String eventName = "VirtualMachine.VM_START";
        EventPacket packet = receiveEventFor(JDWP.EventKind.VM_START, eventName);
//        String versionInfo = getVersionInfo();
//        display("Target VM started:\n" + versionInfo);
    }

   /**
    * Waits for VM_DEATH event from debugee VM.
    */
    public void waitForVMDeath() {
        String eventName = "VirtualMachine.VM_DEATH";
        EventPacket packet = receiveEventFor(JDWP.EventKind.VM_DEATH, eventName);
    }

    /**
     * Wait for class loaded on debugee start up and return its classID.
     * Debuggee should be initially suspended and it will also left suspended
     * by the CLASS_PREPARE event request.
     */
    public long waitForClassLoaded(String className, byte suspendPolicy) {
        // make request for CLASS_PREPARE_EVENT for this class name
        int requestID = requestClassPrepareEvent(className, suspendPolicy);
        // resume initially suspended debugee
        resume();
        // wait for CLASS_PREPARE_EVENT
        return waitForClassPrepareEvent(requestID, className);
    }

    /**
     * Wait for classes loaded on debugee start up and return their classIDs.
     * Debuggee should be initially suspended and it will also left suspended
     * by the CLASS_PREPARE event request.
     */
    public long[] waitForClassesLoaded(String classNames[], byte suspendPolicy) {
        int count = classNames.length;

        // make requests for CLASS_PREPARE_EVENT for these class names
        int[] requestIDs = new int[count];
        for (int i = 0; i < count; i++) {
            requestIDs[i] = requestClassPrepareEvent(classNames[i], suspendPolicy);
        }

        // resume initially suspended debugee
        resume();

        return waitForClassPrepareEvents(requestIDs, classNames);
    }

    /**
     * Wait for breakpoint reached and return threadIDs.
     * Debuggee should be initially suspended and it will also left suspended
     * by the BREAKPOINT event request.
     */
    public long waitForBreakpointReached(long classID, String methodName,
                                                int line, byte suspendPolicy) {
        // query debuggee for methodID
        long methodID = getMethodID(classID, methodName, true);
        // create BREAKPOINT event request
        int requestID = requestBreakpointEvent(JDWP.TypeTag.CLASS, classID, methodID,
                                                                line, suspendPolicy);
        // resume initially suspended debugee
        resume();
        // wait for BREAKPOINT event
        return waitForBreakpointEvent(requestID);
    }

    // --------------------------------------------------- //

    /**
     * Wait for CLASS_PREPARE event made by given request received
     * and return classID.
     * Debuggee will be left suspended by the CLASS_PREPARE event.
     */
    public long waitForClassPrepareEvent(int requestID, String className) {
        String error = "Error occured while waiting for CLASS_PREPARE event for class:\n\t"
                        + className;

        String signature = "L" + className.replace('.', '/') + ";";
        long classID = 0;

        // wait for CLASS_PREPARE event
        for(;;) {
            EventPacket event = receiveEvent();
            byte eventSuspendPolicy = 0;
            long eventThreadID = 0;
            try {
                eventSuspendPolicy = event.getByte();
                int events = event.getInt();
                for (int i = 0; i < events; i++) {
                    // check event kind
                    byte eventKind = event.getByte();
                    if (eventKind == JDWP.EventKind.VM_DEATH) {
                        complain("Unexpected VM_DEATH event received: " + eventKind
                                + " (expected: " + JDWP.EventKind.CLASS_PREPARE +")");
                        throw new Failure(error);
                    } else if (eventKind != JDWP.EventKind.CLASS_PREPARE) {
                        complain("Unexpected event kind received: " + eventKind
                                + " (expected: " + JDWP.EventKind.CLASS_PREPARE +")");
                        throw new Failure(error);
                    }

                    // extract CLASS_PREPARE event specific data
                    int eventRequestID = event.getInt();
                    eventThreadID = event.getObjectID();
                    byte eventRefTypeTag = event.getByte();
                    long eventClassID = event.getReferenceTypeID();
                    String eventClassSignature = event.getString();
                    int eventClassStatus = event.getInt();

                    // check if event was single
                    if (events > 1) {
                        complain("Not single CLASS_PREPARE event received for class:\n\t"
                                    + eventClassSignature);
                        throw new Failure(error);
                    }

                    // check if event is for expected class
                    if (eventClassSignature.equals(signature)) {

                        // check if event is because of expected request
                        if (eventRequestID != requestID) {
                            complain("CLASS_PREPARE event with unexpected requestID ("
                                    + eventRequestID + ") received for class:\n\t"
                                    + eventClassSignature);
                            throw new Failure(error);
                        }

                        // remove event request
                        clearEventRequest(JDWP.EventKind.CLASS_PREPARE, requestID);

                        return eventClassID;
                    } else {
                        complain("Unexpected CLASS_PREPARE event received with class signature:\n"
                               + "  " + eventClassSignature);
                    }

                }

            } catch (BoundException e) {
                complain("Unable to extract data from event packet while waiting for CLASS_PREPARE event:\n\t"
                        + e.getMessage() + "\n" + event);
                throw new Failure(error);
            }

            // resume debuggee according to event suspend policy
            resumeEvent(eventSuspendPolicy, eventThreadID);
        }
    }

    /**
     * Wait for CLASS_PREPARE events made by given requests received
     * and return classIDs.
     * Debuggee will be left suspended by the CLASS_PREPARE event.
     */
    public long[] waitForClassPrepareEvents(int requestIDs[], String classNames[]) {
        int count = classNames.length;
        String error = "Error occured while waiting for " + count + " CLASS_PREPARE events";

        // prepare expected class signatures
        String[] signatures = new String[count];
        for (int i = 0; i < count; i++) {
            signatures[i] = "L" + classNames[i].replace('.', '/') + ";";
        }

        // clear list of classIDs
        long[] classIDs = new long[count];
        for (int i = 0; i < count; i++) {
            classIDs[i] = 0;
        }

        // wait for all expected CLASS_PREPARE events
        int received = 0;
        for(;;) {
            EventPacket event = receiveEvent();
            byte eventSuspendPolicy = 0;
            long eventThreadID = 0;
            try {
                eventSuspendPolicy = event.getByte();
                int events = event.getInt();
                for (int i = 0; i < events; i++) {
                    // check event kind
                    byte eventKind = event.getByte();
                    if (eventKind == JDWP.EventKind.VM_DEATH) {
                        complain("Unexpected VM_DEATH event received: " + eventKind
                                + " (expected: " + JDWP.EventKind.CLASS_PREPARE +")");
                        throw new Failure(error);
                    } else if (eventKind != JDWP.EventKind.CLASS_PREPARE) {
                        complain("Unexpected event kind received: " + eventKind
                                + " (expected: " + JDWP.EventKind.CLASS_PREPARE +")");
                        throw new Failure(error);
                    }

                    // extracy CLASS_PREPARE event specific data
                    int eventRequestID = event.getInt();
                    eventThreadID = event.getObjectID();
                    byte eventRefTypeTag = event.getByte();
                    long eventClassID = event.getReferenceTypeID();
                    String eventClassSignature = event.getString();
                    int eventClassStatus = event.getInt();

                    // check if event was single
                    if (events > 1) {
                        complain("Not single CLASS_PREPARE event received for class:\n\t"
                                + eventClassSignature);
                    }

                    // find appropriate class by signature
                    boolean found = false;
                    for (int j = 0; j < count; j++) {
                        if (eventClassSignature.equals(signatures[j])) {
                            found = true;

                            // check if event is not duplicated
                            if (classIDs[j] != 0) {
                                complain("Extra CLASS_PREPARE event recieved for class:\n\t"
                                        + eventClassSignature);
                            } else {
                                classIDs[j] = eventClassID;
                                received ++;
                            }

                            // check if event is because of expected request
                            if (eventRequestID != requestIDs[j]) {
                                complain("CLASS_PREPARE event with unexpected requestID ("
                                        + requestIDs[j] + ") received for class:\n\t"
                                        + eventClassSignature);
                            } else {
                                clearEventRequest(JDWP.EventKind.CLASS_PREPARE, requestIDs[j]);
                            }
                        }
                    }
                    if (!found) {
                        log.complain("Unexpected CLASS_PREPARE event received with class signature:\n"
                                   + "  " + eventClassSignature);
                    }
                }
            } catch (BoundException e) {
                complain("Unable to extract data from event packet while waiting for CLASS_PREPARE event:\n\t"
                            + e.getMessage() + "\n" + event);
                throw new Failure(error);
            }

            // if all events received return without resuming
            if (received >= count)
                return classIDs;

            // resume debuggee according to events suspend policy
            resumeEvent(eventSuspendPolicy, eventThreadID);
        }
    }

    /**
     * Wait for BREAKPOINT event made by the given request and return threadID.
     * Debuggee will be left suspended by the BREAKPOINT event.
     */
    public long waitForBreakpointEvent(int requestID) {
        String error = "Error occured while waiting for BREAKPOINT event for ";

        for(;;) {
            EventPacket event = receiveEvent();
            byte eventSuspendPolicy = 0;
            long eventThreadID = 0;
            try {
                eventSuspendPolicy = event.getByte();
                int events = event.getInt();
                for (int i = 0; i < events; i++) {
                    // check event kind
                    byte eventKind = event.getByte();
                    if (eventKind == JDWP.EventKind.VM_DEATH) {
                        complain("Unexpected VM_DEATH event received: " + eventKind
                                + " (expected: " + JDWP.EventKind.BREAKPOINT +")");
                        throw new Failure(error);
                    } else if (eventKind != JDWP.EventKind.BREAKPOINT) {
                        complain("Unexpected event kind received: " + eventKind
                                + " (expected: " + JDWP.EventKind.BREAKPOINT +")");
                        throw new Failure(error);
                    }

                    // extrack specific BREAKPOINT event data
                    int eventRequestID = event.getInt();
                    eventThreadID = event.getObjectID();
                    JDWP.Location eventLocation = event.getLocation();

                    if (eventRequestID == requestID) {
                        clearEventRequest(JDWP.EventKind.BREAKPOINT, requestID);
                        return eventThreadID;
                    } else {
                        complain("Unexpected BREAKPOINT event received with requestID: "
                                + eventRequestID + " (expected: " + requestID + ")");
                    }
                }
            } catch (BoundException e) {
                complain("Unable to extract data from event packet while waiting for BREAKPOINT event:\n\t"
                            + e.getMessage() + "\n" + event);
                throw new Failure(error);
            }

            resumeEvent(eventSuspendPolicy, eventThreadID);
        }
    }

    /**
     * Resume debuggee according given event suspend policy.
     */
    public void resumeEvent(byte suspendPolicy, long threadID) {
        if (suspendPolicy == JDWP.SuspendPolicy.NONE) {
            // do nothing
        } else if (suspendPolicy == JDWP.SuspendPolicy.EVENT_THREAD) {
            resumeThread(threadID);
        } else if (suspendPolicy == JDWP.SuspendPolicy.ALL) {
            resume();
        } else {
            throw new Failure("Unexpected event suspend policy while resuming debuggee: "
                            + suspendPolicy);
        }
    }

    // --------------------------------------------------- //

    /**
     * Query target VM for version info.
     */
    public String getVersionInfo() {
        String commandName = "VirtualMachine.Version";
        CommandPacket command =
            new CommandPacket(JDWP.Command.VirtualMachine.Version);
        ReplyPacket reply = receiveReplyFor(command, commandName);

        try {
            String description = reply.getString();
            int jdwpMajor = reply.getInt();
            int jdwpMinor = reply.getInt();
            String vmVersion = reply.getString();
            String vmName = reply.getString();
            return description;
        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting JDWP and VM version info");
        }
    }

   /**
    * Query target VM about VM dependent ID sizes.
    */
    public void queryForIDSizes() {
        String commandName = "VirtualMachine.IDSizes";
        CommandPacket command = new CommandPacket(JDWP.Command.VirtualMachine.IDSizes);
        ReplyPacket reply = receiveReplyFor(command);
        try {
            reply.resetPosition();
            JDWP.TypeSize.FIELD_ID = reply.getInt();
            JDWP.TypeSize.METHOD_ID = reply.getInt();
            JDWP.TypeSize.OBJECT_ID = reply.getInt();
            JDWP.TypeSize.REFERENCE_TYPE_ID = reply.getInt();
            JDWP.TypeSize.FRAME_ID = reply.getInt();
        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting VM dependent ID sizes");
        }
        JDWP.TypeSize.CalculateSizes();
    }

    // --------------------------------------------------- //

    /**
     * Suspend the debugee VM by sending VirtualMachine.Suspend command.
     */
    public void suspend() {
        String commandName = "VirtualMachine.Suspend";
        CommandPacket command = new CommandPacket(JDWP.Command.VirtualMachine.Suspend);
        ReplyPacket reply = receiveReplyFor(command, commandName);
    }

    /**
     * Resume the debugee VM by sending VirtualMachine.Resume command.
     */
    public void resume() {
        String commandName = "VirtualMachine.Resume";
        CommandPacket command = new CommandPacket(JDWP.Command.VirtualMachine.Resume);
        ReplyPacket reply = receiveReplyFor(command, commandName);
    }

    /**
     * Dispose the debugee VM by sending VirtualMachine.Dispose command.
     */
    public void dispose() {
        String commandName = "VirtualMachine.Dispose";
        CommandPacket command = new CommandPacket(JDWP.Command.VirtualMachine.Dispose);
        ReplyPacket reply = receiveReplyFor(command, commandName);
    }

    // --------------------------------------------------- //

    /**
     * Sends JDWP command packet.
     */
    public void sendCommand(CommandPacket packet, String commandName) {
        try {
            transport.write(packet);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            complain("Caught IOException while sending command packet for "
                    + commandName + ":\n\t" + e);
            display("Command packet:\n" + packet);
            throw new Failure("Error occured while sending command: " + commandName);
        }
    }

    /**
     * Receive next JDWP packet.
     */
/*
    public Packet receivePacket() {
        try {
            ReplyPacket packet = new ReplyPacket();
            transport.read(packet);
            return packet;
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught IOException while receiving reply packet:\n\t" + e);
        }
    }
 */
    /**
     * Receive next JDWP reply packet.
     */
    public ReplyPacket receiveReply() {
        try {
            for (;;) {
                Packet packet = new Packet();
                transport.read(packet);

                if (packet.getFlags() == JDWP.Flag.REPLY_PACKET) {
                    ReplyPacket reply = new ReplyPacket(packet);
                    return reply;
                }

                EventPacket event = new EventPacket(packet);
                display("Placing received event packet into queue");
                eventQueue.add(event);
            }
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught IOException while receiving reply packet:\n\t" + e);
        }
    }

    /**
     * Get next JDWP event packet by reading from transport or getting stored
     * event in the event queue.
     */
    public EventPacket getEventPacket() throws IOException {
        // check events queue first
        if (!eventQueue.isEmpty()) {
            EventPacket event = (EventPacket)(eventQueue.removeFirst());
            return event;
        }

        // read from transport
        Packet packet = new Packet();
        transport.read(packet);

        EventPacket event = new EventPacket(packet);
        return event;
    }

    /**
     * Get next JDWP event packet by reading from transport for specified timeout
     * or getting stored event in the event queue.
     */
    public EventPacket getEventPacket(long timeout) throws IOException {
        transport.setReadTimeout(timeout);
        return getEventPacket();
    }

    /**
     * Receive next JDWP event packet.
     */
    public EventPacket receiveEvent() {
        EventPacket packet = null;
        try {
            packet = getEventPacket();
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught IOException while receiving event packet:\n\t" + e);
        }

        if (packet.getFlags() == JDWP.Flag.REPLY_PACKET) {
            ReplyPacket reply = new ReplyPacket(packet);
            log.complain("Unexpected reply packet received with id: "
                        + reply.getPacketID());
            log.display("Reply packet:\n" + reply);
            throw new Failure("Unexpected reply packet received instead of event packet");
        }

        return packet;
    }

    /**
     * Send specified command packet, receive and check reply packet.
     *
     * @throws Failure if exception caught in sending and reading packets
     */
    public ReplyPacket receiveReplyFor(CommandPacket command) {
        return receiveReplyFor(command, Packet.toHexString(command.getCommand(), 4));
    }

    /**
     * Send specified command packet, receive and check reply packet.
     *
     * @throws Failure if exception caught in sending and reading packets
     */
    public ReplyPacket receiveReplyFor(CommandPacket command, String commandName) {
        ReplyPacket reply = null;
        sendCommand(command, commandName);
        reply = receiveReply();
        try {
            reply.checkHeader(command.getPacketID());
        } catch (BoundException e) {
            complain("Wrong header of reply packet for command "+ commandName + ":\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Wrong reply packet received for command: " + commandName);
        }
        return reply;
    }

    /**
     * Receive and check event packet for specified event kind.
     *
     * @throws Failure if exception caught in sending and reading packets
     */
    public EventPacket receiveEventFor(int eventKind, String eventName) {
        EventPacket event = null;
        event = receiveEvent();
        try {
            event.checkHeader(eventKind);
        } catch (BoundException e) {
            complain("Wrong header of event packet for expected "+ eventName + " event:\n\t"
                    + e.getMessage());
            display("Event packet:\n" + event);
            throw new Failure("Wrong event packet received for expected event: " + eventName);
        }
        return event;
    }

    // --------------------------------------------------- //

    /**
     * Check common VM capability.
     */
    public boolean getCapability(int capability, String name) {
        String commandName = "VirtualMachine.Capabilities";

        int count = JDWP.Capability.CAN_GET_MONITOR_INFO + 1;
        if (capability < 0 || capability >= count) {
            throw new TestBug("Illegal capability number (" + capability
                            + ") while checking for VM capability: " + name);
        }

        CommandPacket command =
            new CommandPacket(JDWP.Command.VirtualMachine.Capabilities);
        ReplyPacket reply = receiveReplyFor(command, commandName);

        try {
            reply.resetPosition();

            for (int i = 0; i < count; i++) {
                byte value = reply.getByte();
                if (i == capability) {
                    return (value != 0);
                }
            }

        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting VM capability: "
                                + name);
        }

        throw new TestBug("Illegal capability number (" + capability
                        + ") while checking for VM capability: " + name);
    }

    /**
     * Check new VM capability (since JDWP version 1.4).
     */
    public boolean getNewCapability(int capability, String name) {
        String commandName = "VirtualMachine.CapabilitiesNew";
        int count = JDWP.Capability.CAN_SET_DEFAULT_STRATUM + 1;

        if (capability < 0 || capability >= count) {
            throw new TestBug("Illegal capability number (" + capability
            + ") while checking for VM new capability: " + name);
        }

        CommandPacket command =
            new CommandPacket(JDWP.Command.VirtualMachine.CapabilitiesNew);
        ReplyPacket reply = receiveReplyFor(command, commandName);

        try {
            reply.resetPosition();

            for (int i = 0; i < count; i++) {
                byte value = reply.getByte();
                if (i == capability) {
                    return (value != 0);
                }
            }
        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting VM new capability: "
                                + name);
        }

        throw new TestBug("Illegal capability number (" + capability
                        + ") while checking for VM new capability: " + name);
    }

    // --------------------------------------------------- //

    /**
     * Return ReferenceTypeID for requested class by given signature.
     */
    public long getReferenceTypeID(String classSignature) {
        String commandName = "VirtualMachine.ClassesBySignature";
        CommandPacket command =
            new CommandPacket(JDWP.Command.VirtualMachine.ClassesBySignature);
        command.addString(classSignature);
        command.setLength();
        ReplyPacket reply = receiveReplyFor(command, commandName);

        long typeID = 0;

        try {
            reply.resetPosition();

            int classes = reply.getInt();
            for (int i = 0; i < classes; i++) {
                byte refTypeTag = reply.getByte();
                typeID = reply.getReferenceTypeID();
                int status = reply.getInt();
            }

            if (classes < 0) {
                throw new Failure("Negative number (" + classes
                                + ") of referenceTypeIDs received for signature: "
                                + classSignature);
            }

            if (classes == 0) {
                throw new Failure("No any referenceTypeID received for signature: "
                                + classSignature);
            }

            if (classes > 1) {
                throw new Failure("Too many (" + classes
                                + ") referenceTypeIDs received for signature: "
                                + classSignature);
            }

        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting referenceTypeID for signature: "
                                + classSignature);
        }

        return typeID;
    }

    // --------------------------------------------------- //


    /**
     * Get list of IDs of supertypes (interfaces and classes) for given class.
     */
    public long[] getSupertypes(long classID, boolean declared) {
        Vector<Long> vector = new Vector<Long>();
        addSupertypes(classID, vector, null, null, false, declared);
        return makeListOfLongValues(vector);
    }

    /**
     * Get list of IDs of superclasses for given class.
     */
    public long[] getSuperclasses(long classID, boolean declared) {
        Vector<Long> vector = new Vector<Long>();
        addSupertypes(classID, null, null, vector, false, declared);
        return makeListOfLongValues(vector);
    }

    /**
     * Get list of IDs of implemented interfaces for given class.
     */
    public long[] getImplementedInterfaces(long classID, boolean declared) {
        Vector<Long> vector = new Vector<Long>();
        addSupertypes(classID, null, vector, null, false, declared);
        return makeListOfLongValues(vector);
    }

    /**
     * Get list of IDs of superinterfaces for given interface.
     */
    public long[] getSuperinterfaces(long interfaceID, boolean declared) {
        Vector<Long> vector = new Vector<Long>();
        addSupertypes(interfaceID, null, vector, null, true, declared);
        return makeListOfLongValues(vector);
    }

    // --------------------------------------------------- //

    /**
     * Get list of IDs of methods of given class.
     */
    public long[] getMethodIDs(long classID, boolean declared) {
        Vector<Long> list = new Vector<Long>();
        addMethods(classID, list, null, null, null, false, declared);
        return makeListOfLongValues(list);
    }

    /**
     * Get list of names of methods of given class.
     */
    public String[] getMethodNames(long classID, boolean declared) {
        Vector<String> list = new Vector<String>();
        addMethods(classID, null, list, null, null, false, declared);
        return makeListOfStringValues(list);
    }

    /**
     * Get list of signatures of methods of given class.
     */
    public String[] getMethodSignatures(long classID, boolean declared) {
        Vector<String> list = new Vector<String>();
        addMethods(classID, null, null, list, null, false, declared);
        return makeListOfStringValues(list);
    }

    /**
     * Get ID of a method of given class by name.
     */
    public long getMethodID(long classID, String name, boolean declared) {
        Vector<Long> IDs = new Vector<Long>();
        Vector<String> names = new Vector<String>();
        addMethods(classID, IDs, names, null, null, false, declared);
        int count = names.size();
        for (int i = 0; i < count; i++) {
            if (name.equals(names.elementAt(i))) {
                return (IDs.elementAt(i)).longValue();
            }
        }
        throw new Failure("Method \"" + name + "\" not found for classID: " + classID);
    }

    // --------------------------------------------------- //

    /**
     * Get list of IDs of static fields of given class.
     */
    public long[] getClassFieldIDs(long classID, boolean declared) {
        Vector<Long> list = new Vector<Long>();
        addFields(classID, list, null, null, null, false, declared);
        return makeListOfLongValues(list);
    }

    /**
     * Get list of names of static fields of given class.
     */
    public String[] getClassFieldNames(long classID, boolean declared) {
        Vector<String> list = new Vector<String>();
        addFields(classID, null, list, null, null, false, declared);
        return makeListOfStringValues(list);
    }

    /**
     * Get list of signatures of static fields of given class.
     */
    public String[] getClassFieldSignatures(long classID, boolean declared) {
        Vector<String> list = new Vector<String>();
        addFields(classID, null, null, list, null, false, declared);
        return makeListOfStringValues(list);
    }

    /**
     * Get ID of a static field of given class by name.
     */
    public long getClassFieldID(long classID, String name, boolean declared) {
        Vector<Long> IDs = new Vector<Long>();
        Vector<String> names = new Vector<String>();
        addFields(classID, IDs, names, null, null, false, declared);
        int count = names.size();
        for (int i = 0; i < count; i++) {
            if (name.equals((String)names.elementAt(i))) {
                return ((Long)IDs.elementAt(i)).longValue();
            }
        }
        throw new Failure("Static field \"" + name + "\" not found for classID: " + classID);
    }

    // --------------------------------------------------- //

    /**
     * Get value of a static field of given class.
     */
    public JDWP.Value getStaticFieldValue(long typeID, long fieldID) {
        String commandName = "ReferenceType.GetValues";
        CommandPacket command =
            new CommandPacket(JDWP.Command.ReferenceType.GetValues);
        command.addReferenceTypeID(typeID);
        command.addInt(1);
        command.addFieldID(fieldID);

        ReplyPacket reply = receiveReplyFor(command, commandName);
        JDWP.Value value = null;

        try {
            reply.resetPosition();

            int count = reply.getInt();
            if (count < 1) {
                throw new Failure("No values returned for static fieldID: " + fieldID);
            }
            value = reply.getValue();

        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName +" command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting value of static field: " +
                                + fieldID);
        }
        return value;
    }

    /**
     * Get value of particular type from a static field of given class.
     */
    public JDWP.Value getStaticFieldValue(long typeID, String fieldName, byte tag) {
        long fieldID = getClassFieldID(typeID, fieldName, true);
        JDWP.Value value = getStaticFieldValue(typeID, fieldID);

        if (value.getTag() != tag) {
            complain("unexpedted value tag returned from debuggee: " + value.getTag()
                        + " (expected: " + tag + ")");
            throw new Failure("Error occured while getting value from static field: "
                            + fieldName);
        }
        return value;
    }

    /**
     * Set value of a static field of given class.
     */
    public void setStaticFieldValue(long typeID, long fieldID, JDWP.Value value) {
        String commandName = "ClassType.SetValues";
        CommandPacket command =
            new CommandPacket(JDWP.Command.ClassType.SetValues);
        command.addReferenceTypeID(typeID);
        command.addInt(1);
        command.addFieldID(fieldID);
        command.addUntaggedValue(value, value.getTag());

        ReplyPacket reply = receiveReplyFor(command, commandName);
    }

    /**
     * Get value of a field of given object.
     */
    public JDWP.Value getObjectFieldValue(long objectID, long fieldID) {
        String commandName = "ObjectReference.GetValues";
        CommandPacket command =
            new CommandPacket(JDWP.Command.ObjectReference.GetValues);
        command.addObjectID(objectID);
        command.addInt(1);
        command.addFieldID(fieldID);

        ReplyPacket reply = receiveReplyFor(command, commandName);
        JDWP.Value value = null;

        try {
            reply.resetPosition();

            int count = reply.getInt();
            if (count < 1) {
                throw new Failure("No values returned for object fieldID: " + fieldID);
            }
            value = reply.getValue();

        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting value of object field: " +
                                + fieldID);
        }
        return value;
    }

    /**
     * Set value of a static field of given class.
     */
    public void setObjectFieldValue(long objectID, long fieldID, JDWP.Value value) {
        String commandName = "ObjectReference.SetValues";
        CommandPacket command =
            new CommandPacket(JDWP.Command.ObjectReference.SetValues);
        command.addObjectID(objectID);
        command.addInt(1);
        command.addFieldID(fieldID);
        command.addUntaggedValue(value, value.getTag());

        ReplyPacket reply = receiveReplyFor(command, commandName);
    }

    // --------------------------------------------------- //

    /**
     * Find threadID for given thread name among all active threads.
     */
    public long getThreadID(String name) {
        // request list of all threadIDs
        int threads = 0;
        long threadIDs[] = null;
        {
            String commandName = "VirtualMachine.AllThreads";
            CommandPacket command = new CommandPacket(JDWP.Command.VirtualMachine.AllThreads);
            ReplyPacket reply = receiveReplyFor(command, commandName);
            reply.resetPosition();
            try {
                threads = reply.getInt();
                threadIDs = new long[threads];

                for (int i = 0; i < threads; i++) {
                    threadIDs[i] = reply.getObjectID();
                }
            } catch (BoundException e) {
                complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                        + e.getMessage());
                display("Reply packet:\n" + reply);
                throw new Failure("Error occured while getting threadID for thread name: "
                                    + name);
            }
        }

        // request name for each threadID
        for (int i = 0; i < threads; i++) {
            String commandName = "ThreadReference.Name";
            CommandPacket command = new CommandPacket(JDWP.Command.ThreadReference.Name);
            command.addObjectID(threadIDs[i]);
            ReplyPacket reply = receiveReplyFor(command, commandName);
            try {
                reply.resetPosition();
                String threadName = reply.getString();
                if (threadName.equals(name)) {
                    return threadIDs[i];
                }
            } catch (BoundException e) {
                complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                        + e.getMessage());
                display("Reply packet:\n" + reply);
                throw new Failure("Error occured while getting name for threadID: "
                                + threadIDs[i]);
            }
        }

        throw new Failure("No threadID found for thread name: " + name);
    }

    /**
     * Return thread name for the given threadID.
     */
    public String getThreadName(long threadID) {
        String commandName = "ThreadReference.Name";
        CommandPacket command = new CommandPacket(JDWP.Command.ThreadReference.Name);
        command.addObjectID(threadID);
        ReplyPacket reply = receiveReplyFor(command, commandName);
        try {
            reply.resetPosition();
            String threadName = reply.getString();
            return threadName;
        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting name for threadID: "
                            + threadID);
        }
    }

    /**
     * Suspend thread for the given threadID.
     */
    public void suspendThread(long threadID) {
        String commandName = "ThreadReference.Suspend";
        CommandPacket command = new CommandPacket(JDWP.Command.ThreadReference.Suspend);
        command.addObjectID(threadID);

        ReplyPacket reply = receiveReplyFor(command, commandName);
    }

    /**
     * Resume thread for the given threadID.
     */
    public void resumeThread(long threadID) {
        String commandName = "ThreadReference.resume";
        CommandPacket command = new CommandPacket(JDWP.Command.ThreadReference.Resume);
        command.addObjectID(threadID);

        ReplyPacket reply = receiveReplyFor(command, commandName);
    }

    /**
     * Return frameID for the current frame of the thread.
     * Thread must be suspended.
     */
    public long getCurrentFrameID(long threadID) {
        String commandName = "ThreadReference.Frames";
        CommandPacket command = new CommandPacket(JDWP.Command.ThreadReference.Frames);
        command.addObjectID(threadID);     // threadID
        command.addInt(0);                 // startFrame
        command.addInt(1);                 // length

        ReplyPacket reply = receiveReplyFor(command, commandName);
        try {
            reply.resetPosition();
            int frames = reply.getInt();
            if (frames != 1) {
                throw new Failure("Not only one current frame returned for threadID "
                                + threadID + ": " + frames);
            }
            long frameID = reply.getFrameID();
            JDWP.Location location = reply.getLocation();
            return frameID;
        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting current frame for threadID: "
                            + threadID);
        }
    }

    // --------------------------------------------------- //

    /**
     * Find line number for the given location from the method line table.
     * If <code>approximate</code> is <it>true</i> the found line should begin
     * with code index of the given location. Otherwise, the found line will
     * just cover code index of the given location.
     */
    public int getLineNumber(JDWP.Location location, boolean approximate) {
        String commandName = "Method.LineTable";
        CommandPacket command = new CommandPacket(JDWP.Command.Method.LineTable);
        command.addReferenceTypeID(location.getClassID());
        command.addMethodID(location.getMethodID());
        long index = location.getIndex();
        ReplyPacket reply = receiveReplyFor(command, commandName);
        String msg = "Error occured while getting line number for location: " + location;
        try {
            reply.resetPosition();
            long start = reply.getLong();
            if (index < start) {
                complain("Location index (" + index
                        + ") is less than start method index (" + start);
                throw new Failure(msg);
            }
            long end = reply.getLong();
            if (index > end) {
                complain("Location index (" + index
                        + ") is greater than end method index (" + end);
                throw new Failure(msg);
            }
            int lines = reply.getInt();
            if (!approximate) {
                for (int i = 0; i < lines; i++) {
                    long lineCodeIndex = reply.getLong();
                    int lineNumber = reply.getInt();
                    if (lineCodeIndex == index) {
                        return lineNumber;
                    }
                }
                throw new Failure("No exact line number exactly for location: " + location);
            } else {
                int prevLine = -1;
                for (int i = 0; i < lines; i++) {
                    long lineCodeIndex = reply.getLong();
                    int lineNumber = reply.getInt();
                    if (lineCodeIndex == index) {
                        return lineNumber;
                    } else if (lineCodeIndex > index) {
                        break;
                    }
                    prevLine = lineNumber;
                }
                if (prevLine < 0)
                    throw new Failure("No approximate line number found for location: " + location);
                return prevLine;
            }
        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure(msg);
        }
    }

    /**
     * Find line index for the given line number from the method line table.
     */
    public long getCodeIndex(long classID, long methodID, int lineNumber) {
        String commandName = "Method.LineTable";
        CommandPacket command = new CommandPacket(JDWP.Command.Method.LineTable);
        command.addReferenceTypeID(classID);
        command.addMethodID(methodID);
        ReplyPacket reply = receiveReplyFor(command, commandName);
        String msg = "Error occured while getting code index for line number: " + lineNumber;
        try {
            reply.resetPosition();
            long start = reply.getLong();
            long end = reply.getLong();
            int lines = reply.getInt();
            for (int i = 0; i < lines; i++) {
                long lineCodeIndex = reply.getLong();
                int line = reply.getInt();
                if (lineNumber == line) {
                    return lineCodeIndex;
                }
            }
        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure(msg);
        }

        throw new Failure("No code index found for line number: " + lineNumber);
    }

    // --------------------------------------------------- //

    /**
     * Make the specified event request into debuggee.
     */
    public int requestEvent(CommandPacket requestCommand, String name) {
        String commandName = "EventRequest.Set";
        ReplyPacket reply = receiveReplyFor(requestCommand, name);

        try {
            reply.resetPosition();

            int requestID = reply.getInt();
            return requestID;

        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Request command packet:\n" + requestCommand);
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while making event request: " + name);
        }
    }

    /**
     * Remove existing event request from debuggee.
     */
    public void clearEventRequest(byte eventKind, int requestID) {
        String commandName = "EventRequest.Clear";
        CommandPacket command = new CommandPacket(JDWP.Command.EventRequest.Clear);
        command.addByte(eventKind);
        command.addInt(requestID);
        ReplyPacket reply = receiveReplyFor(command, commandName);
    }

    /**
     * Make request for CLASS_PREPARE event for specified class into debuggee.
     */
    public int requestClassPrepareEvent(String className, byte suspendPolicy) {
        CommandPacket command = new CommandPacket(JDWP.Command.EventRequest.Set);
        command.addByte(JDWP.EventKind.CLASS_PREPARE);
        command.addByte(suspendPolicy);
        command.addInt(1);
            command.addByte(JDWP.EventModifierKind.CLASS_MATCH);
                command.addString(className);

        return requestEvent(command, "CLASS_PREPARE");
    }

    /**
     * Make request for BREAKPOINT event for the specified location into debuggee.
     */
    public int requestBreakpointEvent(JDWP.Location location, byte suspendPolicy) {
        CommandPacket command = new CommandPacket(JDWP.Command.EventRequest.Set);
        command.addByte(JDWP.EventKind.BREAKPOINT);
        command.addByte(suspendPolicy);
        command.addInt(1);
            command.addByte(JDWP.EventModifierKind.LOCATION_ONLY);
                command.addLocation(location);
        return requestEvent(command, "BREAKPOINT");
    }

    /**
     * Make request for BREAKPOINT event for the specified line of the given method.
     */
    public int requestBreakpointEvent(byte typeTag, long classID, long methodID,
                                            int lineNumber, byte suspendPolicy) {
        long codeIndex = getCodeIndex(classID, methodID, lineNumber);
        JDWP.Location location = new JDWP.Location(typeTag, classID, methodID, codeIndex);
        return requestBreakpointEvent(location, suspendPolicy);
    }

    /**
     * Remove all existing BREAKPOINT event requests from debuggee.
     */
    public void clearAllBreakpoints() {
        String commandName = "EventRequest.ClearAllBreakpoints";
        CommandPacket command = new CommandPacket(JDWP.Command.EventRequest.ClearAllBreakpoints);
        ReplyPacket reply = receiveReplyFor(command, commandName);
    }

    // --------------------------------------------------- //

    /**
     * Add IDs of supertypes (interfaces and classes) for given class to the lists.
     */
    private void addSupertypes(long referenceTypeID, Vector<Long> supertypes,
                                Vector<Long> interfaces, Vector<Long> superclasses,
                                boolean interfaceOnly, boolean declared) {

        if (supertypes != null || interfaces != null) {

            // obtain list of direct implemented interfaces
            String commandName = "ReferenceType.Interfaces";
            CommandPacket command = new CommandPacket(JDWP.Command.ReferenceType.Interfaces);
            command.addReferenceTypeID(referenceTypeID);
            ReplyPacket reply = receiveReplyFor(command, commandName);

            try {
                reply.resetPosition();

                int count = reply.getInt();
                if (count < 0) {
                    throw new Failure("Negative number (" + count
                                    + ") of declared interfaces received for referenceTypeID: "
                                    + referenceTypeID);
                }

                for (int i = 0; i < count; i++) {
                    long typeID = reply.getReferenceTypeID();
                    if (!declared) {
                        addSupertypes(typeID, supertypes, interfaces, superclasses,
                                        true, declared);
                    }
                    Long value = Long.valueOf(typeID);
                    if (supertypes != null) {
                        supertypes.add(value);
                    }
                    if (interfaces != null) {
                        interfaces.add(value);
                    }
                }

            } catch (BoundException e) {
                complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                        + e.getMessage());
                display("Reply packet:\n" + reply);
                throw new Failure("Error occured while getting interfeceIDs for referenceTypeID: " +
                                    + referenceTypeID);
            }

        }

        if (!interfaceOnly) {

            String commandName = "ClassType.Superclasses";
            CommandPacket command = new CommandPacket(JDWP.Command.ClassType.Superclass);
            command.addReferenceTypeID(referenceTypeID);
            ReplyPacket reply = receiveReplyFor(command, commandName);

            try {
                reply.resetPosition();

                long typeID = reply.getReferenceTypeID();
                if (typeID != 0) {
                    if (!declared) {
                        addSupertypes(typeID, supertypes, interfaces, superclasses,
                                    false, declared);
                    }
                    Long value = Long.valueOf(typeID);
                    if (supertypes != null) {
                        supertypes.add(value);
                    }
                    if (superclasses != null) {
                        superclasses.add(value);
                    }
                }
            } catch (BoundException e) {
                complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                        + e.getMessage());
                display("Reply packet:\n" + reply);
                throw new Failure("Error occured while getting superclass ID for classID: " +
                                    + referenceTypeID);
            }

        }
    }

    /**
     * Add attributes of fields of given class to the lists.
     */
    private void addFields(long referenceTypeID, Vector<Long> IDs, Vector<String> names,
                                Vector<String> signatures, Vector<Integer> modifiers,
                                boolean interfaceOnly, boolean declared) {

        if (!declared) {
            Vector<Long> supertypes = new Vector<Long>();
            addSupertypes(referenceTypeID, supertypes, null, null, interfaceOnly, declared);
            int count = supertypes.size();
            for (int i = 0; i < count; i++) {
                long typeID = (supertypes.elementAt(i)).longValue();
                addFields(typeID, IDs, names, signatures, modifiers,
                            interfaceOnly, declared);
            }
        }

        String commandName = "ReferenceType.Fields";
        CommandPacket command = new CommandPacket(JDWP.Command.ReferenceType.Fields);
        command.addReferenceTypeID(referenceTypeID);
        ReplyPacket reply = receiveReplyFor(command, commandName);

        try {
            reply.resetPosition();

            int count = reply.getInt();
            if (count < 0) {
                throw new Failure("Negative number (" + count
                                + ") of declared fields received for referenceTypeID: "
                                + referenceTypeID);
            }

            for (int i = 0; i < count; i++) {
                long id = reply.getFieldID();
                String name = reply.getString();
                String signature = reply.getString();
                int modBits = reply.getInt();

                if (IDs != null)
                    IDs.add(Long.valueOf(id));
                if (names != null)
                    names.add(name);
                if (signatures != null)
                    signatures.add(signature);
                if (modifiers != null)
                    modifiers.add(Integer.valueOf(modBits));
            }

        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting fieldIDs for referenceTypeID: " +
                                + referenceTypeID);
        }
    }

    /**
     * Add attributes of methods of given class to the lists.
     */
    private void addMethods(long referenceTypeID, Vector<Long> IDs, Vector<String> names,
                                Vector<String> signatures, Vector<Integer> modifiers,
                                boolean interfaceOnly, boolean declared) {

        if (!declared) {
            Vector<Long> supertypes = new Vector<Long>();
            addSupertypes(referenceTypeID, supertypes, null, null, interfaceOnly, declared);
            int count = supertypes.size();
            for (int i = 0; i < count; i++) {
                long typeID = (supertypes.elementAt(i)).longValue();
                addMethods(typeID, IDs, names, signatures, modifiers,
                            interfaceOnly, declared);
            }
        }

        String commandName = "ReferenceType.Methods";
        CommandPacket command = new CommandPacket(JDWP.Command.ReferenceType.Methods);
        command.addReferenceTypeID(referenceTypeID);
        ReplyPacket reply = receiveReplyFor(command, commandName);

        try {
            reply.resetPosition();

            int count = reply.getInt();
            if (count < 0) {
                throw new Failure("Negative number (" + count
                                + ") of declared fields received for referenceTypeID: "
                                + referenceTypeID);
            }

            for (int i = 0; i < count; i++) {
                long id = reply.getMethodID();
                String name = reply.getString();
                String signature = reply.getString();
                int modBits = reply.getInt();

                if (IDs != null)
                    IDs.add(Long.valueOf(id));
                if (names != null)
                    names.add(name);
                if (signatures != null)
                    signatures.add(signature);
                if (modifiers != null)
                    modifiers.add(Integer.valueOf(modBits));
            }

        } catch (BoundException e) {
            complain("Unable to parse reply packet for " + commandName + " command:\n\t"
                    + e.getMessage());
            display("Reply packet:\n" + reply);
            throw new Failure("Error occured while getting methodIDs for referenceTypeID: " +
                                + referenceTypeID);
        }
    }

    // --------------------------------------------------- //

    private static long[] makeListOfLongValues(Vector<Long> vector) {
        int count = vector.size();
        long[] list = new long[count];
        for (int i = 0; i < count; i++) {
            list[i] = (vector.elementAt(i)).longValue();
        }
        return list;
    }

    private static String[] makeListOfStringValues(Vector<String> vector) {
        int count = vector.size();
        String[] list = new String[count];
        for (int i = 0; i < count; i++) {
            list[i] = vector.elementAt(i);
        }
        return list;
    }

    // --------------------------------------------------- //

    /**
     * Force debugge VM to exit using JDWP connection if possible.
     */
    protected void killDebugee() {
        // ignore
    }

    /**
     * Close transport channel and kill the debugee VM if it is not terminated yet.
     */
    public void close() {
        if (transport != null) {
            try {
                transport.close();
            } catch (IOException e) {
                log.display("WARNING: Caught IOException while closing JDWP connection:\n\t"
                            + e);
            }
        }
        super.close();
    }

}
