/*
 * Copyright (c) 2001, 2018, 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.jdi;

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

import com.sun.jdi.*;
import com.sun.jdi.connect.*;

import com.sun.jdi.connect.Connector.Argument;
import java.io.*;
import java.net.*;
import java.util.*;

/**
 * This class provides debugger with connection to debugee VM
 * using JDI connectors.
 *<p>
 * This class provides abilities to launch and bind to debugee VM
 * as described for base <code>DebugeeBinder</code> class,
 * using JDI connectors and <code>com.sun.VirtualMachine</code> mirror.
 * <p>
 * When <code>Binder</code> is asked to bind to debugee by invoking
 * <code>bindToBebugee()</code> method it uses
 * <code>com.sun.jdi.Connector</code> object corresponding to
 * value of command line options <code>-connector</code> and
 * <code>-transport</code> to launch and connect to debugee VM.
 * After debugee is launched and connection is established
 * <code>Binder</code> uses <code>com.sun.jdi.VirtualMachine</code>
 * object to construct <code>Debugee</code> object, that
 * provides abilities to interact with debugee VM.
 *
 * @see Debugee
 * @see DebugeeBinder
 */
public class Binder extends DebugeeBinder {

    /**
     * Default message prefix for <code>Binder</code> object.
     */
    public static final String LOG_PREFIX = "binder> ";

    /**
     * Get version string.
     */
    public static String getVersion () {
        return "@(#)Binder.java 1.14 03/10/08";
    }

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

    /**
     * Handler of command line arguments.
     */
    private ArgumentHandler argumentHandler = null;

    /**
     * Return <code>argumentHandler</code> of this binder.
     */
    public ArgumentHandler getArgumentHandler() {
        return argumentHandler;
    }

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

    /**
     * Make <code>Binder</code> object and pass raw command line arguments.
     *
     * @deprecated  Use newer
     *              <code>Binder(ArgumentHandler,Log)</code>
     *              constructor.
     */
    public Binder (String args[]) {
        this(args, new Log(System.err));
    }

    /**
     * Make <code>Binder</code> object for raw command line arguments
     * and specified <code>log</code> object.
     *
     * @deprecated  Use newer
     *              <code>Binder(ArgumentHandler,Log)</code>
     *              constructor.
     */
    public Binder (String args[], Log log) {
        this(new ArgumentHandler(args), log);
    }

    /**
     * Make <code>Binder</code> object for specified command line arguments
     * and <code>log</code> object.
     */
    public Binder (ArgumentHandler argumentHandler, Log log) {
        super(argumentHandler, log);
        this.argumentHandler = argumentHandler;
    }

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

    /**
     * Make initial <code>Debugee</code> object for local debuggee process
     * started with launching connector.
     */
    public Debugee makeLocalDebugee(Process process) {
        LocalLaunchedDebugee debugee = new LocalLaunchedDebugee(process, this);

        Finalizer finalizer = new Finalizer(debugee);
        finalizer.activate();

        return debugee;
    }

    /**
     * Launch local debuggee process with specified command line
     * and make initial <code>Debugee</code> object.
     */
    public Debugee startLocalDebugee(String cmd) {
        Process process = null;

        try {
            process = launchProcess(cmd);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while launching local debuggee VM process:\n\t"
                            + e);
        }

        return makeLocalDebugee(process);
    }

    /**
     * Make debuggee wrapper for already launched debuggee VM.
     * After enwraping debugee's output is redirected to Binder's log,
     * VMStartEvent is received and debuggee is initialized.
     */
    public Debugee enwrapDebugee(VirtualMachine vm, Process proc) {
        Debugee debugee = makeLocalDebugee(proc);

        display("Redirecting VM output");
        debugee.redirectOutput(log);
        debugee.setupVM(vm);

        long timeout = argumentHandler.getWaitTime() * 60 * 1000; // milliseconds

        display("Waiting for VM initialized");
        debugee.waitForVMInit(timeout);

        return debugee;
    }

    /**
     * Launch debugee VM and establish connection to it without waiting for VMStartEvent.
     * After launching debugee's output is redirected to Binder's log,
     * but VMStartEvent is not received and so debuggee is not fully initialized.
     *
     * @see #bindToDebugee(String)
     */
    public Debugee bindToDebugeeNoWait(String classToExecute) {

        VirtualMachineManager vmm = Bootstrap.virtualMachineManager();
        display("VirtualMachineManager: version "
                + vmm.majorInterfaceVersion() + "."
                + vmm.minorInterfaceVersion());

        Debugee debugee = null;

        String classPath = null;
//        classPath = System.getProperty("java.class.path");

        prepareForPipeConnection(argumentHandler);

        if (argumentHandler.isLaunchedLocally()) {

            if (argumentHandler.isDefaultConnector()) {
                debugee = localDefaultLaunchDebugee(vmm, classToExecute, classPath);
            } else if (argumentHandler.isRawLaunchingConnector()) {
                debugee = localRawLaunchDebugee(vmm, classToExecute, classPath);
            } else if (argumentHandler.isLaunchingConnector()) {
                debugee = localLaunchDebugee(vmm, classToExecute, classPath);
            } else if (argumentHandler.isAttachingConnector()) {
                debugee = localLaunchAndAttachDebugee(vmm, classToExecute, classPath);
            } else if (argumentHandler.isLaunchingConnector()) {
                debugee = localLaunchDebugee(vmm, classToExecute, classPath);
            } else if (argumentHandler.isListeningConnector()) {
                debugee = localLaunchAndListenDebugee(vmm, classToExecute, classPath);
            } else {
                throw new TestBug("Unexpected connector type for local debugee launch mode"
                                  + argumentHandler.getConnectorType());
            }

        } else if (argumentHandler.isLaunchedRemotely()) {

            connectToBindServer(classToExecute);

            if (argumentHandler.isAttachingConnector()) {
                debugee = remoteLaunchAndAttachDebugee(vmm, classToExecute, classPath);
            } else if (argumentHandler.isListeningConnector()) {
                debugee = remoteLaunchAndListenDebugee(vmm, classToExecute, classPath);
            } else {
                throw new TestBug("Unexpected connector type for remote debugee launch mode"
                                  + argumentHandler.getConnectorType());
            }

        } else if (argumentHandler.isLaunchedManually()) {

            if (argumentHandler.isAttachingConnector()) {
                debugee = manualLaunchAndAttachDebugee(vmm, classToExecute, classPath);
            } else if (argumentHandler.isListeningConnector()) {
                debugee = manualLaunchAndListenDebugee(vmm, classToExecute, classPath);
            } else {
                throw new TestBug("Unexpected connector type for manual debugee launch mode"
                                  + argumentHandler.getConnectorType());
            }

        } else {
            throw new Failure("Unexpected debugee launching mode: " + argumentHandler.getLaunchMode());
        }

        return debugee;
    }

    /**
     * Launch debugee VM and establish JDI connection.
     * After launching debugee's output is redirected to Binder's log,
     * VMStart event is received and debuggee is initialized.
     *
     * @see #bindToDebugeeNoWait(String)
     */
    public Debugee bindToDebugee(String classToExecute) {
        Debugee debugee = bindToDebugeeNoWait(classToExecute);

        if(argumentHandler.getOptions().getProperty("traceAll") != null)
            debugee.VM().setDebugTraceMode(VirtualMachine.TRACE_ALL);

        long timeout = argumentHandler.getWaitTime() * 60 * 1000; // milliseconds

        display("Waiting for VM initialized");
        debugee.waitForVMInit(timeout);

        return debugee;
    }

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

    /**
     * Launch debugee locally via the default LaunchingConnector.
     */
    private Debugee localDefaultLaunchDebugee (VirtualMachineManager vmm,
                                                String classToExecute,
                                                String classPath) {
        display("Finding connector: " + "default" );
        LaunchingConnector connector = vmm.defaultConnector();
        Map<String,? extends Argument> arguments = setupLaunchingConnector(connector, classToExecute, classPath);

        VirtualMachine vm;
        try {
            display("Launching debugee");
            vm = connector.launch(arguments);
        } catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace(log.getOutStream());
            throw new TestBug("Wrong connector arguments used to launch debuggee VM:\n\t" + e);
        } catch (VMStartException e) {
            e.printStackTrace(log.getOutStream());
            String msg = readVMStartExceptionOutput(e, log.getOutStream());
            throw new Failure("Caught exception while starting debugee VM:\n\t" + e + "\n" + msg);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while launching debugee VM:\n\t" + e);
        };

        Process process = vm.process();
        Debugee debugee = makeLocalDebugee(process);
        debugee.redirectOutput(log);
        debugee.setupVM(vm);

        return debugee;
    }


    /**
     * Launch debugee locally via the default LaunchingConnector.
     */
    private Debugee localLaunchDebugee (VirtualMachineManager vmm,
                                            String classToExecute,
                                            String classPath) {

        display("Finding connector: " + argumentHandler.getConnectorName() );
        LaunchingConnector connector =
            (LaunchingConnector) findConnector(argumentHandler.getConnectorName(),
                                                vmm.launchingConnectors());
        Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = setupLaunchingConnector(connector, classToExecute, classPath);

        VirtualMachine vm;
        try {
            display("Launching debugee");
            vm = connector.launch(arguments);
        } catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace(log.getOutStream());
            throw new TestBug("Wrong connector arguments used to launch debuggee VM:\n\t" + e);
        } catch (VMStartException e) {
            e.printStackTrace(log.getOutStream());
            String msg = readVMStartExceptionOutput(e, log.getOutStream());
            throw new Failure("Caught exception while starting debugee VM:\n\t" + e + "\nProcess output:\n\t" + msg);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while launching debugee VM:\n\t" + e);
        };

        Process process = vm.process();
        Debugee debugee = makeLocalDebugee(process);
        debugee.redirectOutput(log);
        debugee.setupVM(vm);

        return debugee;
    }

    /**
     * Launch debugee locally via the RawLaunchingConnector.
     */
    private Debugee localRawLaunchDebugee (VirtualMachineManager vmm,
                                            String classToExecute,
                                            String classPath) {
        display("Finding connector: " + argumentHandler.getConnectorName() );
        LaunchingConnector connector =
            (LaunchingConnector) findConnector(argumentHandler.getConnectorName(),
                                                vmm.launchingConnectors());
        Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = setupRawLaunchingConnector(connector, classToExecute, classPath);

        VirtualMachine vm;
        try {
            display("Launching debugee");
            vm = connector.launch(arguments);
        } catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace(log.getOutStream());
            throw new TestBug("Wrong connector arguments used to launch debuggee VM:\n\t" + e);
        } catch (VMStartException e) {
            e.printStackTrace(log.getOutStream());
            String msg = readVMStartExceptionOutput(e, log.getOutStream());
            throw new Failure("Caught exception while starting debugee VM:\n\t" + e + "\nProcess output:\n\t" + msg);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while launching debugee VM:\n\t" + e);
        };

        Process process = vm.process();
        Debugee debugee = makeLocalDebugee(process);
        debugee.redirectOutput(log);
        debugee.setupVM(vm);

        return debugee;
    }

    /**
     * Launch debugee VM locally as a local process and connect to it using
     * <code>AttachingConnector</code>.
     */
    private Debugee localLaunchAndAttachDebugee (VirtualMachineManager vmm,
                                                    String classToExecute,
                                                    String classPath) {
        display("FindingConnector: " + argumentHandler.getConnectorName() );
        AttachingConnector connector =
            (AttachingConnector) findConnector(argumentHandler.getConnectorName(),
                                                vmm.attachingConnectors());
        Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = setupAttachingConnector(connector, classToExecute, classPath);

        String address = makeTransportAddress();
        String[] cmdLineArgs = makeCommandLineArgs(classToExecute, address);
        String javaCmdLine = makeCommandLineString(classToExecute, address, "\"");

        display("Starting java process:\n\t" + javaCmdLine);
        Debugee debugee = startLocalDebugee(cmdLineArgs);
        debugee.redirectOutput(log);

        display("Attaching to debugee");
        VirtualMachine vm = null;
        IOException ioe = null;
        for (int i = 0; i < CONNECT_TRIES; i++) {
            try {
                vm = connector.attach(arguments);
                display("Debugee attached");
                debugee.setupVM(vm);
                return debugee;
            } catch (IOException e) {
                display("Attempt #" + i + " to connect to debugee VM failed:\n\t" + e);
                ioe = e;
                if (debugee.terminated()) {
                    throw new Failure("Unable to connect to debuggee VM: VM process is terminated");
                }
                try {
                    Thread.currentThread().sleep(CONNECT_TRY_DELAY);
                } catch (InterruptedException ie) {
                    ie.printStackTrace(log.getOutStream());
                    throw new Failure("Thread interrupted while pausing connection attempts:\n\t"
                                    + ie);
                }
            } catch (IllegalConnectorArgumentsException e) {
                e.printStackTrace(log.getOutStream());
                throw new TestBug("Wrong connector arguments used to attach to debuggee VM:\n\t" + e);
            }
        }
        throw new Failure("Unable to connect to debugee VM after " + CONNECT_TRIES
                        + " tries:\n\t" + ioe);
    }

    /**
     * Launch debugee VM locally as a local process and connect to it using
     * <code>ListeningConnector</code>.
     */
    private Debugee localLaunchAndListenDebugee (VirtualMachineManager vmm,
                                                    String classToExecute,
                                                    String classPath) {
        display("Finding connector: " + argumentHandler.getConnectorName() );
        ListeningConnector connector =
            (ListeningConnector) findConnector(argumentHandler.getConnectorName(),
                                                vmm.listeningConnectors());
        Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = setupListeningConnector(connector, classToExecute, classPath);

        String address = null;
        try {
            display("Listening for connection from debugee");
            address = connector.startListening(arguments);
        } catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace(log.getOutStream());
            throw new TestBug("Wrong connector arguments used to listen debuggee VM:\n\t" + e);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while starting listening debugee VM:\n\t" + e);
        };

        String[] cmdLineArgs = makeCommandLineArgs(classToExecute, address);
        String javaCmdLine = makeCommandLineString(classToExecute, address, "\"");

        display("Starting java process:\n\t" + javaCmdLine);
        Debugee debugee = startLocalDebugee(cmdLineArgs);
        debugee.redirectOutput(log);

        display("Waiting for connection from debugee");
        VirtualMachine vm = null;
        IOException ioe = null;
        for (int i = 0; i < CONNECT_TRIES; i++) {
            try {
                vm = connector.accept(arguments);
                connector.stopListening(arguments);
                display("Debugee attached");
                debugee.setupVM(vm);
                return debugee;
            } catch (IOException e) {
                display("Attempt #" + i + " to listen debugee VM failed:\n\t" + e);
                ioe = e;
                if (debugee.terminated()) {
                    throw new Failure("Unable to connect to debuggee VM: VM process is terminated");
                }
                try {
                    Thread.currentThread().sleep(CONNECT_TRY_DELAY);
                } catch (InterruptedException ie) {
                    ie.printStackTrace(log.getOutStream());
                    throw new Failure("Thread interrupted while pausing connection attempts:\n\t"
                                    + ie);
                }
            } catch (IllegalConnectorArgumentsException e) {
                e.printStackTrace(log.getOutStream());
                throw new TestBug("Wrong connector arguments used to listen debuggee VM:\n\t" + e);
            }
        }
        throw new Failure("Unable to connect to debugee VM after " + CONNECT_TRIES
                        + " tries:\n\t" + ioe);
    }

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

    /**
     * Launch debugee VM remotely via <code>BindServer</code> and connect to it using
     * <code>AttachingConnector</code>.
     */
    private Debugee remoteLaunchAndAttachDebugee (VirtualMachineManager vmm,
                                                    String classToExecute,
                                                    String classPath) {
        display("Finding connector: " + argumentHandler.getConnectorName() );
        AttachingConnector connector =
            (AttachingConnector) findConnector(argumentHandler.getConnectorName(),
                                                vmm.attachingConnectors());

        Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = setupAttachingConnector(connector, classToExecute, classPath);

        String address = makeTransportAddress();
        String[] cmdLineArgs = makeCommandLineArgs(classToExecute, address);
        String javaCmdLine = makeCommandLineString(classToExecute, address, "\"");

        display("Starting remote java process:\n\t" + javaCmdLine);
        Debugee debugee = startRemoteDebugee(cmdLineArgs);

        display("Attaching to debugee");
        VirtualMachine vm;
        IOException ioe = null;
        for (int i = 0; i < CONNECT_TRIES; i++) {
            try {
                vm = connector.attach(arguments);
                display("Debugee attached");
                debugee.setupVM(vm);
                return debugee;
            } catch (IOException e) {
                display("Attempt #" + i + " to connect to debugee VM failed:\n\t" + e);
                ioe = e;
                if (debugee.terminated()) {
                    throw new Failure("Unable to connect to debuggee VM: VM process is terminated");
                }
                try {
                    Thread.currentThread().sleep(CONNECT_TRY_DELAY);
                } catch (InterruptedException ie) {
                    ie.printStackTrace(log.getOutStream());
                    throw new Failure("Thread interrupted while pausing connection attempts:\n\t"
                                    + ie);
                }
            } catch (IllegalConnectorArgumentsException e) {
                e.printStackTrace(log.getOutStream());
                throw new TestBug("Wrong connector arguments used to attach to debuggee VM:\n\t" + e);
            }
        }
        throw new Failure("Unable to connect to debugee VM after " + CONNECT_TRIES
                        + " tries:\n\t" + ioe);
    }

    /**
     * Launch debugee VM remotely via <code>BindServer</code> and connect to it using
     * <code>ListeningConnector</code>.
     */
    private Debugee remoteLaunchAndListenDebugee (VirtualMachineManager vmm,
                                                    String classToExecute,
                                                    String classPath) {
        display("Finding connector: " + argumentHandler.getConnectorName() );
        ListeningConnector connector =
            (ListeningConnector) findConnector(argumentHandler.getConnectorName(),
                                                vmm.listeningConnectors());
        Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = setupListeningConnector(connector, classToExecute, classPath);

        String address = null;
        try {
            display("Listening for connection from debugee");
            address = connector.startListening(arguments);
        } catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace(log.getOutStream());
            throw new TestBug("Wrong connector arguments used to listen debuggee VM:\n\t" + e);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while starting listening debugee VM:\n\t" + e);
        };

        String[] cmdLineArgs = makeCommandLineArgs(classToExecute, address);
        String javaCmdLine = makeCommandLineString(classToExecute, address, "\"");

        display("Starting remote java process:\n\t" + javaCmdLine);
        Debugee debugee = startRemoteDebugee(cmdLineArgs);

        display("Waiting for connection from debugee");
        VirtualMachine vm;
        IOException ioe = null;
        for (int i = 0; i < CONNECT_TRIES; i++) {
            try {
                vm = connector.accept(arguments);
                connector.stopListening(arguments);
                display("Debugee attached");
                debugee.setupVM(vm);
                return debugee;
            } catch (IOException e) {
                display("Attempt #" + i + " to listen debugee VM failed:\n\t" + e);
                ioe = e;
                if (debugee.terminated()) {
                    throw new Failure("Unable to connect to debuggee VM: VM process is terminated");
                }
                try {
                    Thread.currentThread().sleep(CONNECT_TRY_DELAY);
                } catch (InterruptedException ie) {
                    ie.printStackTrace(log.getOutStream());
                    throw new Failure("Thread interrupted while pausing connection attempts:\n\t"
                                    + ie);
                }
            } catch (IllegalConnectorArgumentsException e) {
                e.printStackTrace(log.getOutStream());
                throw new TestBug("Wrong connector arguments used to listen debuggee VM:\n\t" + e);
            }
        }
        throw new Failure("Unable to connect to debugee VM after " + CONNECT_TRIES
                        + " tries:\n\t" + ioe);
    }

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

    /**
     * Prompt to manually launch debugee VM and connect to it using
     * <code>AttachingConnector</code>.
     */
    private Debugee manualLaunchAndAttachDebugee (VirtualMachineManager vmm,
                                                    String classToExecute,
                                                    String classPath) {
        display("Finding connector: " + argumentHandler.getConnectorName() );
        AttachingConnector connector =
            (AttachingConnector) findConnector(argumentHandler.getConnectorName(),
                                                vmm.attachingConnectors());
        Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = setupAttachingConnector(connector, classToExecute, classPath);

        String address = makeTransportAddress();
        String javaCmdLine = makeCommandLineString(classToExecute, address, "\"");

        display("Starting manual java process:\n\t" + javaCmdLine);
        ManualLaunchedDebugee debugee = startManualDebugee(javaCmdLine);

        VirtualMachine vm;
        try {
            display("Attaching to debugee");
            vm = connector.attach(arguments);
        } catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace(log.getOutStream());
            throw new TestBug("Wrong connector arguments used to attach to debuggee VM:\n\t" + e);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while attaching to debugee VM:\n\t" + e);
        };
        display("Debugee attached");

        debugee.setupVM(vm);
        return debugee;
    }

    /**
     * Prompt to manually launch debugee VM and connect to it using
     * <code>ListeningConnector</code>.
     */
    private Debugee manualLaunchAndListenDebugee (VirtualMachineManager vmm,
                                                    String classToExecute,
                                                    String classPath) {
        display("Finding connector: " + argumentHandler.getConnectorName() );
        ListeningConnector connector =
            (ListeningConnector) findConnector(argumentHandler.getConnectorName(),
                                                vmm.listeningConnectors());
        Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = setupListeningConnector(connector, classToExecute, classPath);

        VirtualMachine vm;
        try {
            display("Listening for connection from debugee");
            String address = connector.startListening(arguments);
            String javaCmdLine = makeCommandLineString(classToExecute, address, "\"");
            display("Starting manual java process:\n\t" + javaCmdLine);
            ManualLaunchedDebugee debugee = startManualDebugee(javaCmdLine);
            display("Waiting for connection from debugee");
            vm = connector.accept(arguments);
            display("Debugee attached");
            connector.stopListening(arguments);
            debugee.setupVM(vm);
            return debugee;
        } catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace(log.getOutStream());
            throw new TestBug("Wrong connector arguments used to listen debuggee VM:\n\t" + e);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while listening to debugee VM:\n\t" + e);
        }
    }

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

    /**
     * Make proper arguments for LaunchingConnector.
     */
    private Map<String,? extends Argument> setupLaunchingConnector(LaunchingConnector connector,
                                                String classToExecute,
                                                String classPath) {
        display("LaunchingConnector:");
        display("    name: " + connector.name());
        display("    description: " + connector.description());
        display("    transport: " + connector.transport());

        Hashtable<String,? extends Argument> arguments = new Hashtable<String,Argument>(connector.defaultArguments());

        Connector.Argument arg;

        arg = (Connector.StringArgument) arguments.get("quote");
        String quote = arg.value();

        String cmdline = classToExecute + " " +
                ArgumentHandler.joinArguments(argumentHandler.getRawArguments(), quote);

        arg = (Connector.StringArgument) arguments.get("main");
        arg.setValue(cmdline);

        if (! argumentHandler.willDebugeeSuspended()) {
            Connector.BooleanArgument barg = (Connector.BooleanArgument) arguments.get("suspend");
            barg.setValue(true);
        }

/*
        if (! argumentHandler.isJVMDIStrictMode()) {
            arg = (Connector.StringArgument) arguments.get("options");
            arg.setValue("strict=y");
        }
 */

        if (! argumentHandler.isDefaultDebugeeJavaHome()) {
            arg = (Connector.StringArgument) arguments.get("home");
            arg.setValue(argumentHandler.getDebugeeJavaHome());
        }

        if (! argumentHandler.isDefaultLaunchExecName()) {
            arg = (Connector.StringArgument) arguments.get("vmexec");
            arg.setValue(argumentHandler.getLaunchExecName());
        }

        String vmArgs = "";

        String vmUserArgs = argumentHandler.getLaunchOptions();

        if (vmUserArgs != null) {
            vmArgs = vmUserArgs;
        }

/*
        if (classPath != null) {
            vmArgs += " -classpath " + quote + classPath + quote;
        }
 */

        if (vmArgs.length() > 0) {
            arg = (Connector.StringArgument) arguments.get("options");
            arg.setValue(vmArgs);
        }

        display("Connector arguments:");
        Iterator iterator = arguments.values().iterator();
        while (iterator.hasNext()) {
            display("    " + iterator.next());
        }
        return arguments;
    }

    /**
     * Make proper arguments for RawLaunchingConnector.
     */
    private Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> setupRawLaunchingConnector(LaunchingConnector connector,
                                                String classToExecute,
                                                String classPath) {
        display("RawLaunchingConnector:");
        display("    name: " + connector.name());
        display("    description: " + connector.description());
        display("    transport: " + connector.transport());

        Hashtable<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = new Hashtable<java.lang.String, com.sun.jdi.connect.Connector.Argument>(connector.defaultArguments());

        String connectorAddress;
        String vmAddress;

        if (argumentHandler.isSocketTransport()) {
            connectorAddress = argumentHandler.getTransportPort();
            vmAddress = argumentHandler.getDebugeeHost()
                        + ":" + argumentHandler.getTransportPort();
        } else if (argumentHandler.isShmemTransport() ) {
            connectorAddress = argumentHandler.getTransportSharedName();
            vmAddress=connectorAddress;
        } else {
            throw new TestBug("Undefined transport type for AttachingConnector");
        }

        Connector.Argument arg;

        arg = (Connector.StringArgument) arguments.get("quote");
        String quote = arg.value();

        String javaCmdLine = makeCommandLineString(classToExecute, quote);

        arg = (Connector.StringArgument) arguments.get("command");
        arg.setValue(javaCmdLine);

        arg = (Connector.StringArgument) arguments.get("address");
        arg.setValue(connectorAddress);

        display("Connector arguments:");
        Iterator iterator = arguments.values().iterator();
        while (iterator.hasNext()) {
            display("    " + iterator.next());
        }
        return arguments;
    }

    /**
     * Make proper arguments for AttachingConnector.
     */
    private Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> setupAttachingConnector(AttachingConnector connector,
                                                String classToExecute,
                                                String classPath) {
        display("AttachingConnector:");
        display("    name: " + connector.name());
        display("    description: " + connector.description());
        display("    transport: " + connector.transport());

        Hashtable<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = new Hashtable<java.lang.String,com.sun.jdi.connect.Connector.Argument>(connector.defaultArguments());

        Connector.Argument arg;
        if (argumentHandler.isSocketTransport()) {
            arg = (Connector.StringArgument) arguments.get("hostname");
            arg.setValue(argumentHandler.getDebugeeHost());
            Connector.IntegerArgument iarg = (Connector.IntegerArgument) arguments.get("port");
            iarg.setValue(argumentHandler.getTransportPortNumber());
        } else {
            arg = (Connector.StringArgument) arguments.get("name");
            arg.setValue(argumentHandler.getTransportSharedName());
        }

        display("Connector arguments:");
        Iterator iterator = arguments.values().iterator();
        while (iterator.hasNext()) {
            display("    " + iterator.next());
        }
        return arguments;
    }

    /**
     * Make proper arguments for ListeningConnector.
     */
    private Map<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> setupListeningConnector(ListeningConnector connector,
                                                String classToExecute,
                                                String classPath) {
        display("ListeningConnector:");
        display("    name: " + connector.name());
        display("    description: " + connector.description());
        display("    transport: " + connector.transport());

        Hashtable<java.lang.String,? extends com.sun.jdi.connect.Connector.Argument> arguments = new Hashtable<java.lang.String,com.sun.jdi.connect.Connector.Argument>(connector.defaultArguments());

        Connector.Argument arg;
        if (argumentHandler.isSocketTransport()) {
            if (!argumentHandler.isTransportAddressDynamic()) {
                int port = argumentHandler.getTransportPortNumber();
                Connector.IntegerArgument iarg = (Connector.IntegerArgument) arguments.get("port");
                iarg.setValue(port);
            }
        } else {
            String sharedName = argumentHandler.getTransportSharedName();
            arg = (Connector.StringArgument) arguments.get("name");
            arg.setValue(sharedName);
        }

        display("Connector arguments:");
        Iterator iterator = arguments.values().iterator();
        while (iterator.hasNext()) {
            display("    " + iterator.next());
        }
        return arguments;
    }

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

    /**
     * Find connector by name from given connectors list.
     */
    private Connector findConnector(String connectorName, List connectors) {
        Iterator iter = connectors.iterator();

        while (iter.hasNext()) {
            Connector connector = (Connector) iter.next();
            if (connector.name().equals(connectorName)) {
                return connector;
            }
        }
        throw new Failure("JDI connector not found: " + connectorName);
    }

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

    /**
     * Launch local debuggee process with specified command line arguments
     * and make initial <code>Debugee</code> mirror.
     */
    protected Debugee startLocalDebugee(String[] cmdArgs) {
        Process process = null;

        try {
            process = launchProcess(cmdArgs);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while launching local debuggee VM process:\n\t"
                            + e);
        }

        return makeLocalDebugee(process);
    }

    /**
     * Launch remote debuggee process with specified command line arguments
     * and make initial <code>Debugee</code> mirror.
     */
    protected RemoteLaunchedDebugee startRemoteDebugee(String[] cmdArgs) {
        try {
            launchRemoteProcess(cmdArgs);
        } catch (IOException e) {
            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while launching remote debuggee VM process:\n\t"
                            + e);
        }

        RemoteLaunchedDebugee debugee = new RemoteLaunchedDebugee(this);

        Finalizer finalizer = new Finalizer(debugee);
        finalizer.activate();

        return debugee;
    }

    /**
     * Launch manual debuggee process with specified command line arguments
     * and make initial <code>Debugee</code> mirror.
     */
    protected ManualLaunchedDebugee startManualDebugee(String cmd) {
        ManualLaunchedDebugee debugee = new ManualLaunchedDebugee(this);
        debugee.launchDebugee(cmd);

        Finalizer finalizer = new Finalizer(debugee);
        finalizer.activate();

        return debugee;
    }

    public static String readVMStartExceptionOutput(VMStartException e, PrintStream log) {
        StringBuffer msg = new StringBuffer();
        try (InputStream is = e.process().getInputStream()) {
            msg.append("\tstdout: ").append(new String(readAllBytes(is))).append('\n');
        } catch (IOException e1) {
            log.println("Could not read normal output from launched process:" + e1);
        }
        try (InputStream is = e.process().getErrorStream()) {
            msg.append("\tstderr: ").append(new String(readAllBytes(is)));
        } catch (IOException e1) {
            log.println("Could not read error output from launched process:" + e1);
        }
        return msg.toString();
    }

    /**
     * Copied from the JDK 9 implementation in InputStream.java
     */
    private static byte[] readAllBytes(InputStream is) throws IOException {
        final int DEFAULT_BUFFER_SIZE = 8192;
        final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;

        byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
        int capacity = buf.length;
        int nread = 0;
        int n;
        for (;;) {
            // read to EOF which may read more or less than initial buffer size
            while ((n = is.read(buf, nread, capacity - nread)) > 0)
                nread += n;

            // if the last call to read returned -1, then we're done
            if (n < 0)
                break;

            // need to allocate a larger buffer
            if (capacity <= MAX_BUFFER_SIZE - capacity) {
                capacity = capacity << 1;
            } else {
                if (capacity == MAX_BUFFER_SIZE)
                    throw new OutOfMemoryError("Required array size too large");
                capacity = MAX_BUFFER_SIZE;
            }
            buf = Arrays.copyOf(buf, capacity);
        }
        return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
    }

}


/**
 * Mirror of locally launched debugee.
 */
final class LocalLaunchedDebugee extends Debugee {

    /** Enwrap the locally started VM process. */
    public LocalLaunchedDebugee (Process process, Binder binder) {
        super(binder);
        this.process = process;
        checkTermination = true;
    }

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

    /** Return exit status of the debugee VM. */
    public int getStatus () {
        return process.exitValue();
    }

    /** Check whether the debugee VM has been terminated. */
    public boolean terminated () {
        if (process == null)
            return true;

        try {
            int value = process.exitValue();
            return true;
        } catch (IllegalThreadStateException e) {
            return false;
        }
    }

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

    /** Kill the debugee VM. */
    protected void killDebugee () {
        super.killDebugee();
        if (!terminated()) {
            log.display("Killing debugee VM process");
            process.destroy();
        }
    }

    /** Wait until the debugee VM shutdown or crash. */
    protected int waitForDebugee () throws InterruptedException {
        int code = process.waitFor();
        return code;
    }

    /** Get a pipe to write to the debugee's stdin stream. */
    protected OutputStream getInPipe () {
        return process.getOutputStream();
    }

    /** Get a pipe to read the debugee's stdout stream. */
    protected InputStream getOutPipe () {
        return process.getInputStream();
    }

    /** Get a pipe to read the debugee's stderr stream. */
    protected InputStream getErrPipe () {
        return process.getErrorStream();
    }
}


/**
 * Mirror of remotely launched debugee.
 */
final class RemoteLaunchedDebugee extends Debugee {

    /** Enwrap the remotely started VM process. */
    public RemoteLaunchedDebugee (Binder binder) {
        super(binder);
    }

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

    /** Return exit status of the debugee VM. */
    public int getStatus () {
        return binder.getRemoteProcessStatus();
    }

    /** Check whether the debugee VM has been terminated. */
    public boolean terminated () {
        return binder.isRemoteProcessTerminated();
    }

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

    /** Kill the debugee VM. */
    protected void killDebugee () {
        super.killDebugee();
        if (!terminated()) {
            binder.killRemoteProcess();
        }
    }

    /** Wait until the debugee VM shutdown or crash. */
    protected int waitForDebugee () {
        return binder.waitForRemoteProcess();
    }

    /** Get a pipe to write to the debugee's stdin stream. */
    protected OutputStream getInPipe () {
        return null;
    }

    /** Get a pipe to read the debugee's stdout stream. */
    protected InputStream getOutPipe () {
        return null;
    }

    /** Get a pipe to read the debugee's stderr stream. */
    protected InputStream getErrPipe () {
        return null;
    }

    public void redirectStdout(OutputStream out) {
    }

    public void redirectStdout(Log log, String prefix) {
    }

    public void redirectStderr(OutputStream out) {
    }

    public void redirectStderr(Log log, String prefix) {
    }
}


/**
 * Mirror of manually launched debugee.
 */
final class ManualLaunchedDebugee extends Debugee {
    /** Enwrap the manually started VM process. */
    public ManualLaunchedDebugee (Binder binder) {
        super(binder);
        makeInputReader();
    }

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

    private int exitCode = 0;
    private boolean finished = false;
    private static BufferedReader bin = null;

    public void launchDebugee(String commandLine) {
        makeInputReader();

        putMessage("Launch target VM using such command line:\n"
                    + commandLine);
        String answer = askQuestion("Has the VM successfully started? (yes/no)", "yes");
        for ( ; ; ) {
            if (answer.equals("yes"))
                break;
            if (answer.equals("no"))
                throw new Failure ("Unable to manually launch debugee VM");
            answer = askQuestion("Wrong answer. Please type yes or no", "yes");
        }
    }

    private static void makeInputReader() {
        if (bin == null) {
            bin = new BufferedReader(new InputStreamReader(System.in));
        }
    }

    private static void destroyInputReader() {
        if (bin != null) {
            try {
                bin.close();
            } catch (IOException e) {
//                e.printStackTrace(log.getOutStream());
                throw new Failure("Caught exception while closing input stream:\n\t" + e);
            }
            bin = null;
        }
    }

    private static void putMessage(String msg) {
        System.out.println("\n>>> " + msg);
    }

    private static String askQuestion(String question, String defaultAnswer) {
        try {
            System.out.print("\n>>> " + question);
            System.out.print(" [" + defaultAnswer + "] ");
            System.out.flush();
            String answer = bin.readLine();
            if (answer.equals(""))
                return defaultAnswer;
            return answer;
        } catch (IOException e) {
//            e.printStackTrace(log.getOutStream());
            throw new Failure("Caught exception while reading answer:\n\t" + e);
        }
    }

    /** Return exit status of the debugee VM. */
    public int getStatus () {
        if (! finished) {
            throw new Failure("Unable to get status of debugee VM: process still alive");
        }
        return exitCode;
    }

    /** Check whether the debugee VM has been terminated. */
    public boolean terminated () {
        return finished;
    }

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

    /** Kill the debugee VM. */
    protected void killDebugee () {
        super.killDebugee();
        if (!terminated()) {
            putMessage("Kill launched VM");
            String answer = askQuestion("Has the VM successfully terminated? (yes/no)", "yes");
            for ( ; ; ) {
                if (answer.equals("yes")) {
                    finished = true;
                    break;
                }
                if (answer.equals("no"))
                    throw new Failure ("Unable to manually kill debugee VM");
                answer = askQuestion("Wrong answer. Please type yes or no", "yes");
            }
        }
    }

    /** Wait until the debugee VM shutdown or crash. */
    protected int waitForDebugee () {
        putMessage("Wait for launched VM to exit.");
        String answer = askQuestion("What is VM exit code?", "95");
        for ( ; ; ) {
            try {
                exitCode = Integer.parseInt(answer);
                break;
            } catch (NumberFormatException e) {
                answer = askQuestion("Wrong answer. Please type integer value", "95");
            }
        }
        finished = true;
        return exitCode;
    }

    /** Get a pipe to write to the debugee's stdin stream. */
    protected OutputStream getInPipe () {
        return null;
    }

    /** Get a pipe to read the debugee's stdout stream. */
    protected InputStream getOutPipe () {
        return null;
    }

    /** Get a pipe to read the debugee's stderr stream. */
    protected InputStream getErrPipe () {
        return null;
    }

    public void redirectStdout(OutputStream out) {
    }

    public void redirectStdout(Log log, String prefix) {
    }

    public void redirectStderr(OutputStream out) {
    }

    public void redirectStderr(Log log, String prefix) {
    }

    public void close() {
        destroyInputReader();
        super.close();
    }
}
