8160127: JShell API: extract abstract JDI and abstract streaming implementations of ExecutionControl
authorrfield
Wed, 20 Jul 2016 23:19:09 -0700
changeset 39807 ba0ff343d241
parent 39806 d3a13ca6013e
child 39808 7aadfb85605f
8160127: JShell API: extract abstract JDI and abstract streaming implementations of ExecutionControl 8159935: JShell API: Reorganize execution support code into jdk.jshell.execution (previously sent for review, and combined here) 8160128: JShell API: extract abstract streaming remote agent 8159122: JShell API: Configurable invocation mechanism Summary: ExecutionControl implementation support with simplified ExecutionControl interface Reviewed-by: jlahoda
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/ClassTracker.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/FailOverExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIConnection.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIEventHandler.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDINotConnectedException.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteAgent.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteClassLoader.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteCodes.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/ClassTracker.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DefaultLoaderDelegate.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DemultiplexInput.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DirectExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/ExecutionControlForwarder.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Internal.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIDefaultExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIEventHandler.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIInitiator.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/LoaderDelegate.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/MultiplexingOutputStream.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/PipeInputStream.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteCodes.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/SPIResolutionException.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java
langtools/test/jdk/jshell/ComputeFQNsTest.java
langtools/test/jdk/jshell/FailOverExecutionControlTest.java
langtools/test/jdk/jshell/JDIListeningExecutionControlTest.java
langtools/test/jdk/jshell/KullaTesting.java
langtools/test/jdk/jshell/LocalExecutionControl.java
langtools/test/jdk/jshell/UserExecutionControlTest.java
langtools/test/jdk/jshell/UserJDIUserRemoteTest.java
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/ClassTracker.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,130 +0,0 @@
-/*
- * Copyright (c) 2016, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.internal.jshell.jdi;
-
-import java.util.HashMap;
-import java.util.Objects;
-import com.sun.jdi.ReferenceType;
-import java.util.List;
-import com.sun.jdi.VirtualMachine;
-
-/**
- * Tracks the state of a class.
- */
-class ClassTracker {
-
-    private final VirtualMachine vm;
-    private final HashMap<String, ClassInfo> map;
-
-    ClassTracker(VirtualMachine vm) {
-        this.vm = vm;
-        this.map = new HashMap<>();
-    }
-
-    /**
-     * Associates a class name, class bytes, and ReferenceType.
-     */
-    class ClassInfo {
-
-        // The name of the class -- always set
-        private final String className;
-
-        // The corresponding compiled class bytes when a load or redefine
-        // is started.  May not be the loaded bytes.  May be null.
-        private byte[] bytes;
-
-        // The class bytes successfully loaded/redefined into the remote VM.
-        private byte[] loadedBytes;
-
-        // The corresponding JDI ReferenceType.  Used by redefineClasses and
-        // acts as indicator of successful load (null if not loaded).
-        private ReferenceType rt;
-
-        private ClassInfo(String className) {
-            this.className = className;
-        }
-
-        String getClassName() {
-            return className;
-        }
-
-        byte[] getLoadedBytes() {
-            return loadedBytes;
-        }
-
-        byte[] getBytes() {
-            return bytes;
-        }
-
-        private void setBytes(byte[] potentialBytes) {
-            this.bytes = potentialBytes;
-        }
-
-        // The class has been successful loaded redefined.  The class bytes
-        // sent are now actually loaded.
-        void markLoaded() {
-            loadedBytes = bytes;
-        }
-
-        // Ask JDI for the ReferenceType, null if not loaded.
-        ReferenceType getReferenceTypeOrNull() {
-            if (rt == null) {
-                rt = nameToRef(className);
-            }
-            return rt;
-        }
-
-        private ReferenceType nameToRef(String name) {
-            List<ReferenceType> rtl = vm.classesByName(name);
-            if (rtl.size() != 1) {
-                return null;
-            }
-            return rtl.get(0);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            return o instanceof ClassInfo
-                    && ((ClassInfo) o).className.equals(className);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(this.className);
-        }
-    }
-
-    // Map a class name to the current compiled class bytes.
-    ClassInfo classInfo(String className, byte[] bytes) {
-        ClassInfo ci = get(className);
-        ci.setBytes(bytes);
-        return ci;
-    }
-
-    // Lookup the ClassInfo by class name, create if it does not exist.
-    ClassInfo get(String className) {
-        return map.computeIfAbsent(className, k -> new ClassInfo(k));
-    }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/FailOverExecutionControl.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-/*
- * Copyright (c) 2016, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.internal.jshell.jdi;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import jdk.internal.jshell.debug.InternalDebugControl;
-import jdk.jshell.JShellException;
-import jdk.jshell.spi.ExecutionControl;
-import jdk.jshell.spi.ExecutionEnv;
-
-/**
- * A meta implementation of ExecutionControl which cycles through the specified
- * ExecutionControl instances until it finds one that starts.
- */
-public class FailOverExecutionControl implements ExecutionControl {
-
-    private final List<ExecutionControl> ecl = new ArrayList<>();
-    private ExecutionControl active = null;
-    private final List<Exception> thrown = new ArrayList<>();
-
-    /**
-     * Create the ExecutionControl instance with at least one actual
-     * ExecutionControl instance.
-     *
-     * @param ec0 the first instance to try
-     * @param ecs the second and on instance to try
-     */
-    public FailOverExecutionControl(ExecutionControl ec0, ExecutionControl... ecs) {
-        ecl.add(ec0);
-        for (ExecutionControl ec : ecs) {
-            ecl.add(ec);
-        }
-    }
-
-    @Override
-    public void start(ExecutionEnv env) throws Exception {
-        for (ExecutionControl ec : ecl) {
-            try {
-                ec.start(env);
-                // Success! This is our active ExecutionControl
-                active = ec;
-                return;
-            } catch (Exception ex) {
-                thrown.add(ex);
-            } catch (Throwable ex) {
-                thrown.add(new RuntimeException(ex));
-            }
-            InternalDebugControl.debug(env.state(), env.userErr(),
-                    thrown.get(thrown.size() - 1), "failed one in FailOverExecutionControl");
-        }
-        // They have all failed -- rethrow the first exception we encountered
-        throw thrown.get(0);
-    }
-
-    @Override
-    public void close() {
-        active.close();
-    }
-
-    @Override
-    public boolean addToClasspath(String path) {
-        return active.addToClasspath(path);
-    }
-
-    @Override
-    public String invoke(String classname, String methodname) throws JShellException {
-        return active.invoke(classname, methodname);
-    }
-
-    @Override
-    public boolean load(Collection<String> classes) {
-        return active.load(classes);
-    }
-
-    @Override
-    public boolean redefine(Collection<String> classes) {
-        return active.redefine(classes);
-    }
-
-    @Override
-    public ClassStatus getClassStatus(String classname) {
-        return active.getClassStatus(classname);
-    }
-
-    @Override
-    public void stop() {
-        active.stop();
-    }
-
-    @Override
-    public String varValue(String classname, String varname) {
-        return active.varValue(classname, varname);
-    }
-
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIConnection.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,346 +0,0 @@
-/*
- * Copyright (c) 1998, 2016, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-/*
- * This source code is provided to illustrate the usage of a given feature
- * or technique and has been deliberately simplified. Additional steps
- * required for a production-quality application, such as security checks,
- * input validation and proper error handling, might not be present in
- * this sample code.
- */
-
-
-package jdk.internal.jshell.jdi;
-
-import com.sun.jdi.*;
-import com.sun.jdi.connect.*;
-
-import java.util.*;
-import java.util.Map.Entry;
-import java.io.*;
-import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
-
-/**
- * Connection to a Java Debug Interface VirtualMachine instance.
- * Adapted from jdb VMConnection. Message handling, exception handling, and I/O
- * redirection changed.  Interface to JShell added.
- */
-class JDIConnection {
-
-    private static final String REMOTE_AGENT = "jdk.internal.jshell.remote.RemoteAgent";
-
-    private VirtualMachine vm;
-    private boolean active = true;
-    private Process process = null;
-    private int outputCompleteCount = 0;
-
-    private final JDIExecutionControl ec;
-    private final Connector connector;
-    private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
-    private final int traceFlags;
-
-    private synchronized void notifyOutputComplete() {
-        outputCompleteCount++;
-        notifyAll();
-    }
-
-    private synchronized void waitOutputComplete() {
-        // Wait for stderr and stdout
-        if (process != null) {
-            while (outputCompleteCount < 2) {
-                try {wait();} catch (InterruptedException e) {}
-            }
-        }
-    }
-
-    private Connector findConnector(String name) {
-        for (Connector cntor :
-                 Bootstrap.virtualMachineManager().allConnectors()) {
-            if (cntor.name().equals(name)) {
-                return cntor;
-            }
-        }
-        return null;
-    }
-
-    private Map <String, Connector.Argument> mergeConnectorArgs(Connector connector, Map<String, String> argumentName2Value) {
-        Map<String, Connector.Argument> arguments = connector.defaultArguments();
-
-        for (Entry<String, String> argumentEntry : argumentName2Value.entrySet()) {
-            String name = argumentEntry.getKey();
-            String value = argumentEntry.getValue();
-            Connector.Argument argument = arguments.get(name);
-
-            if (argument == null) {
-                throw new IllegalArgumentException("Argument is not defined for connector:" +
-                                          name + " -- " + connector.name());
-            }
-
-            argument.setValue(value);
-        }
-
-        return arguments;
-    }
-
-    /**
-     * The JShell specific Connector args for the LaunchingConnector.
-     *
-     * @param portthe socket port for (non-JDI) commands
-     * @param remoteVMOptions any user requested VM options
-     * @return the argument map
-     */
-    private static Map<String, String> launchArgs(int port, String remoteVMOptions) {
-        Map<String, String> argumentName2Value = new HashMap<>();
-        argumentName2Value.put("main", REMOTE_AGENT + " " + port);
-        argumentName2Value.put("options", remoteVMOptions);
-        return argumentName2Value;
-    }
-
-    /**
-     * Start the remote agent and establish a JDI connection to it.
-     *
-     * @param ec the execution control instance
-     * @param port the socket port for (non-JDI) commands
-     * @param remoteVMOptions any user requested VM options
-     * @param isLaunch does JDI do the launch? That is, LaunchingConnector,
-     * otherwise we start explicitly and use ListeningConnector
-     */
-    JDIConnection(JDIExecutionControl ec, int port, List<String> remoteVMOptions, boolean isLaunch) {
-        this(ec,
-                isLaunch
-                        ? "com.sun.jdi.CommandLineLaunch"
-                        : "com.sun.jdi.SocketListen",
-                isLaunch
-                        ? launchArgs(port, String.join(" ", remoteVMOptions))
-                        : new HashMap<>(),
-                0);
-        if (isLaunch) {
-            vm = launchTarget();
-        } else {
-            vm = listenTarget(port, remoteVMOptions);
-        }
-
-        if (isOpen() && vm().canBeModified()) {
-            /*
-             * Connection opened on startup.
-             */
-            new JDIEventHandler(vm(), (b) -> ec.handleVMExit())
-                    .start();
-        }
-    }
-
-    /**
-     * Base constructor -- set-up a JDI connection.
-     *
-     * @param ec the execution control instance
-     * @param connectorName the standardized name of the connector
-     * @param argumentName2Value the argument map
-     * @param traceFlags should we trace JDI behavior
-     */
-    JDIConnection(JDIExecutionControl ec, String connectorName, Map<String, String> argumentName2Value, int traceFlags) {
-        this.ec = ec;
-        this.connector = findConnector(connectorName);
-        if (connector == null) {
-            throw new IllegalArgumentException("No connector named: " + connectorName);
-        }
-        connectorArgs = mergeConnectorArgs(connector, argumentName2Value);
-        this.traceFlags = traceFlags;
-    }
-
-    final synchronized VirtualMachine vm() {
-        if (vm == null) {
-            throw new JDINotConnectedException();
-        } else {
-            return vm;
-        }
-    }
-
-    private synchronized boolean isOpen() {
-        return (vm != null);
-    }
-
-    synchronized boolean isRunning() {
-        return process != null && process.isAlive();
-    }
-
-    // Beginning shutdown, ignore any random dying squeals
-    void beginShutdown() {
-        active = false;
-    }
-
-    synchronized void disposeVM() {
-        try {
-            if (vm != null) {
-                vm.dispose(); // This could NPE, so it is caught below
-                vm = null;
-            }
-        } catch (VMDisconnectedException ex) {
-            // Ignore if already closed
-        } catch (Throwable e) {
-            ec.debug(DBG_GEN, null, "disposeVM threw: " + e);
-        } finally {
-            if (process != null) {
-                process.destroy();
-                process = null;
-            }
-            waitOutputComplete();
-        }
-    }
-
-    private void dumpStream(InputStream inStream, final PrintStream pStream) throws IOException {
-        BufferedReader in =
-            new BufferedReader(new InputStreamReader(inStream));
-        int i;
-        try {
-            while ((i = in.read()) != -1) {
-                // directly copy input to output, but skip if asked to close
-                if (active) {
-                    pStream.print((char) i);
-                }
-            }
-        } catch (IOException ex) {
-            String s = ex.getMessage();
-            if (active && !s.startsWith("Bad file number")) {
-                throw ex;
-            }
-            // else we are being shutdown (and don't want any spurious death
-            // throws to ripple) or
-            // we got a Bad file number IOException which just means
-            // that the debuggee has gone away.  We'll just treat it the
-            // same as if we got an EOF.
-        }
-    }
-
-    /**
-     *  Create a Thread that will retrieve and display any output.
-     *  Needs to be high priority, else debugger may exit before
-     *  it can be displayed.
-     */
-    private void displayRemoteOutput(final InputStream inStream, final PrintStream pStream) {
-        Thread thr = new Thread("output reader") {
-            @Override
-            public void run() {
-                try {
-                    dumpStream(inStream, pStream);
-                } catch (IOException ex) {
-                    ec.debug(ex, "Failed reading output");
-                    ec.handleVMExit();
-                } finally {
-                    notifyOutputComplete();
-                }
-            }
-        };
-        thr.setPriority(Thread.MAX_PRIORITY-1);
-        thr.start();
-    }
-
-    /**
-     *  Create a Thread that will ship all input to remote.
-     *  Does it need be high priority?
-     */
-    private void readRemoteInput(final OutputStream outStream, final InputStream inputStream) {
-        Thread thr = new Thread("input reader") {
-            @Override
-            public void run() {
-                try {
-                    byte[] buf = new byte[256];
-                    int cnt;
-                    while ((cnt = inputStream.read(buf)) != -1) {
-                        outStream.write(buf, 0, cnt);
-                        outStream.flush();
-                    }
-                } catch (IOException ex) {
-                    ec.debug(ex, "Failed reading output");
-                    ec.handleVMExit();
-                }
-            }
-        };
-        thr.setPriority(Thread.MAX_PRIORITY-1);
-        thr.start();
-    }
-
-    private void forwardIO() {
-        displayRemoteOutput(process.getErrorStream(), ec.execEnv.userErr());
-        displayRemoteOutput(process.getInputStream(), ec.execEnv.userOut());
-        readRemoteInput(process.getOutputStream(), ec.execEnv.userIn());
-    }
-
-    /* launch child target vm */
-    private VirtualMachine launchTarget() {
-        LaunchingConnector launcher = (LaunchingConnector)connector;
-        try {
-            VirtualMachine new_vm = launcher.launch(connectorArgs);
-            process = new_vm.process();
-            forwardIO();
-            return new_vm;
-        } catch (Exception ex) {
-            reportLaunchFail(ex, "launch");
-        }
-        return null;
-    }
-
-    /**
-     * Directly launch the remote agent and connect JDI to it with a
-     * ListeningConnector.
-     */
-    private VirtualMachine listenTarget(int port, List<String> remoteVMOptions) {
-        ListeningConnector listener = (ListeningConnector) connector;
-        try {
-            // Start listening, get the JDI connection address
-            String addr = listener.startListening(connectorArgs);
-            ec.debug(DBG_GEN, "Listening at address: " + addr);
-
-            // Launch the RemoteAgent requesting a connection on that address
-            String javaHome = System.getProperty("java.home");
-            List<String> args = new ArrayList<>();
-            args.add(javaHome == null
-                    ? "java"
-                    : javaHome + File.separator + "bin" + File.separator + "java");
-            args.add("-agentlib:jdwp=transport=" + connector.transport().name() +
-                    ",address=" + addr);
-            args.addAll(remoteVMOptions);
-            args.add(REMOTE_AGENT);
-            args.add("" + port);
-            ProcessBuilder pb = new ProcessBuilder(args);
-            process = pb.start();
-
-            // Forward out, err, and in
-            forwardIO();
-
-            // Accept the connection from the remote agent
-            vm = listener.accept(connectorArgs);
-            listener.stopListening(connectorArgs);
-            return vm;
-        } catch (Exception ex) {
-            reportLaunchFail(ex, "listen");
-        }
-        return null;
-    }
-
-    private void reportLaunchFail(Exception ex, String context) {
-        throw new InternalError("Failed remote " + context + ": " + connector +
-                " -- " + connectorArgs, ex);
-    }
-}
\ No newline at end of file
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIEventHandler.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,182 +0,0 @@
-/*
- * Copyright (c) 1998, 2014, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.internal.jshell.jdi;
-
-import java.util.function.Consumer;
-import com.sun.jdi.*;
-import com.sun.jdi.event.*;
-
-/**
- * Handler of Java Debug Interface events.
- * Adapted from jdb EventHandler; Handling of events not used by JShell stubbed out.
- */
-class JDIEventHandler implements Runnable {
-
-    private final Thread thread;
-    private volatile boolean connected = true;
-    private boolean completed = false;
-    private final VirtualMachine vm;
-    private final Consumer<Boolean> reportVMExit;
-
-    JDIEventHandler(VirtualMachine vm, Consumer<Boolean> reportVMExit) {
-        this.vm = vm;
-        this.reportVMExit = reportVMExit;
-        this.thread = new Thread(this, "event-handler");
-    }
-
-    void start() {
-        thread.start();
-    }
-
-    synchronized void shutdown() {
-        connected = false;  // force run() loop termination
-        thread.interrupt();
-        while (!completed) {
-            try {wait();} catch (InterruptedException exc) {}
-        }
-    }
-
-    @Override
-    public void run() {
-        EventQueue queue = vm.eventQueue();
-        while (connected) {
-            try {
-                EventSet eventSet = queue.remove();
-                boolean resumeStoppedApp = false;
-                EventIterator it = eventSet.eventIterator();
-                while (it.hasNext()) {
-                    resumeStoppedApp |= handleEvent(it.nextEvent());
-                }
-
-                if (resumeStoppedApp) {
-                    eventSet.resume();
-                }
-            } catch (InterruptedException exc) {
-                // Do nothing. Any changes will be seen at top of loop.
-            } catch (VMDisconnectedException discExc) {
-                handleDisconnectedException();
-                break;
-            }
-        }
-        synchronized (this) {
-            completed = true;
-            notifyAll();
-        }
-    }
-
-    private boolean handleEvent(Event event) {
-        if (event instanceof ExceptionEvent) {
-            exceptionEvent(event);
-        } else if (event instanceof WatchpointEvent) {
-            fieldWatchEvent(event);
-        } else if (event instanceof MethodEntryEvent) {
-            methodEntryEvent(event);
-        } else if (event instanceof MethodExitEvent) {
-            methodExitEvent(event);
-        } else if (event instanceof ClassPrepareEvent) {
-            classPrepareEvent(event);
-        } else if (event instanceof ThreadStartEvent) {
-            threadStartEvent(event);
-        } else if (event instanceof ThreadDeathEvent) {
-            threadDeathEvent(event);
-        } else if (event instanceof VMStartEvent) {
-            vmStartEvent(event);
-            return true;
-        } else {
-            handleExitEvent(event);
-        }
-        return true;
-    }
-
-    private boolean vmDied = false;
-
-    private void handleExitEvent(Event event) {
-        if (event instanceof VMDeathEvent) {
-            vmDied = true;
-        } else if (event instanceof VMDisconnectEvent) {
-            connected = false;
-        } else {
-            throw new InternalError("Unexpected event type: " +
-                    event.getClass());
-        }
-        reportVMExit.accept(vmDied);
-    }
-
-    private synchronized void handleDisconnectedException() {
-        /*
-         * A VMDisconnectedException has happened while dealing with
-         * another event. We need to flush the event queue, dealing only
-         * with exit events (VMDeath, VMDisconnect) so that we terminate
-         * correctly.
-         */
-        EventQueue queue = vm.eventQueue();
-        while (connected) {
-            try {
-                EventSet eventSet = queue.remove();
-                EventIterator iter = eventSet.eventIterator();
-                while (iter.hasNext()) {
-                    handleExitEvent(iter.next());
-                }
-            } catch (InterruptedException exc) {
-                // ignore
-            } catch (InternalError exc) {
-                // ignore
-            }
-        }
-    }
-
-    private void vmStartEvent(Event event)  {
-        VMStartEvent se = (VMStartEvent)event;
-    }
-
-    private void methodEntryEvent(Event event)  {
-        MethodEntryEvent me = (MethodEntryEvent)event;
-    }
-
-    private void methodExitEvent(Event event)  {
-        MethodExitEvent me = (MethodExitEvent)event;
-    }
-
-    private void fieldWatchEvent(Event event)  {
-        WatchpointEvent fwe = (WatchpointEvent)event;
-    }
-
-    private void classPrepareEvent(Event event)  {
-        ClassPrepareEvent cle = (ClassPrepareEvent)event;
-    }
-
-    private void exceptionEvent(Event event) {
-        ExceptionEvent ee = (ExceptionEvent)event;
-    }
-
-    private void threadDeathEvent(Event event) {
-        ThreadDeathEvent tee = (ThreadDeathEvent)event;
-    }
-
-    private void threadStartEvent(Event event) {
-        ThreadStartEvent tse = (ThreadStartEvent)event;
-    }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIExecutionControl.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,598 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.internal.jshell.jdi;
-
-import static jdk.internal.jshell.remote.RemoteCodes.*;
-import java.io.DataInputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.PrintStream;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.io.EOFException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import com.sun.jdi.BooleanValue;
-import com.sun.jdi.ClassNotLoadedException;
-import com.sun.jdi.IncompatibleThreadStateException;
-import com.sun.jdi.InvalidTypeException;
-import com.sun.jdi.ObjectReference;
-import com.sun.jdi.ReferenceType;
-import com.sun.jdi.StackFrame;
-import com.sun.jdi.ThreadReference;
-import com.sun.jdi.VirtualMachine;
-import static java.util.stream.Collectors.toList;
-import jdk.jshell.JShellException;
-import jdk.jshell.spi.ExecutionControl;
-import jdk.jshell.spi.ExecutionEnv;
-import jdk.internal.jshell.jdi.ClassTracker.ClassInfo;
-import static java.util.stream.Collectors.toMap;
-import jdk.internal.jshell.debug.InternalDebugControl;
-import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
-
-/**
- * Controls the remote execution environment.
- * Interfaces to the JShell-core by implementing ExecutionControl SPI.
- * Interfaces to RemoteAgent over a socket and via JDI.
- * Launches a remote process.
- */
-public class JDIExecutionControl implements ExecutionControl {
-
-    ExecutionEnv execEnv;
-    private final boolean isLaunch;
-    private JDIConnection connection;
-    private ClassTracker classTracker;
-    private Socket socket;
-    private ObjectInputStream remoteIn;
-    private ObjectOutputStream remoteOut;
-
-    /**
-     * Creates an ExecutionControl instance based on JDI.
-     *
-     * @param isLaunch true for LaunchingConnector; false for ListeningConnector
-     */
-    public JDIExecutionControl(boolean isLaunch) {
-        this.isLaunch = isLaunch;
-    }
-
-    /**
-     * Creates an ExecutionControl instance based on a JDI LaunchingConnector.
-     */
-    public JDIExecutionControl() {
-        this.isLaunch = true;
-    }
-
-    /**
-     * Initializes the launching JDI execution engine. Initialize JDI and use it
-     * to launch the remote JVM. Set-up control and result communications socket
-     * to the remote execution environment. This socket also transports the
-     * input/output channels.
-     *
-     * @param execEnv the execution environment provided by the JShell-core
-     * @throws IOException
-     */
-    @Override
-    public void start(ExecutionEnv execEnv) throws IOException {
-        this.execEnv = execEnv;
-        StringBuilder sb = new StringBuilder();
-        try (ServerSocket listener = new ServerSocket(0)) {
-            // timeout after 60 seconds
-            listener.setSoTimeout(60000);
-            int port = listener.getLocalPort();
-            connection = new JDIConnection(this, port, execEnv.extraRemoteVMOptions(), isLaunch);
-            this.socket = listener.accept();
-            // out before in -- match remote creation so we don't hang
-            this.remoteOut = new ObjectOutputStream(socket.getOutputStream());
-            PipeInputStream commandIn = new PipeInputStream();
-            new DemultiplexInput(socket.getInputStream(), commandIn, execEnv.userOut(), execEnv.userErr()).start();
-            this.remoteIn = new ObjectInputStream(commandIn);
-        }
-    }
-
-    /**
-     * Closes the execution engine. Send an exit command to the remote agent.
-     * Shuts down the JDI connection. Should this close the socket?
-     */
-    @Override
-    public void close() {
-        try {
-            if (connection != null) {
-                connection.beginShutdown();
-            }
-            if (remoteOut != null) {
-                remoteOut.writeInt(CMD_EXIT);
-                remoteOut.flush();
-            }
-            if (connection != null) {
-                connection.disposeVM();
-            }
-        } catch (IOException ex) {
-            debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
-        }
-    }
-
-    /**
-     * Loads the list of classes specified. Sends a load command to the remote
-     * agent with pairs of classname/bytes.
-     *
-     * @param classes the names of the wrapper classes to loaded
-     * @return true if all classes loaded successfully
-     */
-    @Override
-    public boolean load(Collection<String> classes) {
-        try {
-            // Create corresponding ClassInfo instances to track the classes.
-            // Each ClassInfo has the current class bytes associated with it.
-            List<ClassInfo> infos = withBytes(classes);
-            // Send a load command to the remote agent.
-            remoteOut.writeInt(CMD_LOAD);
-            remoteOut.writeInt(classes.size());
-            for (ClassInfo ci : infos) {
-                remoteOut.writeUTF(ci.getClassName());
-                remoteOut.writeObject(ci.getBytes());
-            }
-            remoteOut.flush();
-            // Retrieve and report results from the remote agent.
-            boolean result = readAndReportResult();
-            // For each class that now has a JDI ReferenceType, mark the bytes
-            // as loaded.
-            infos.stream()
-                    .filter(ci -> ci.getReferenceTypeOrNull() != null)
-                    .forEach(ci -> ci.markLoaded());
-            return result;
-        } catch (IOException ex) {
-            debug(DBG_GEN, "IOException on remote load operation: %s\n", ex);
-            return false;
-        }
-    }
-
-    /**
-     * Invoke the doit method on the specified class.
-     *
-     * @param classname name of the wrapper class whose doit should be invoked
-     * @return return the result value of the doit
-     * @throws JShellException if a user exception was thrown (EvalException) or
-     * an unresolved reference was encountered (UnresolvedReferenceException)
-     */
-    @Override
-    public String invoke(String classname, String methodname) throws JShellException {
-        try {
-            synchronized (STOP_LOCK) {
-                userCodeRunning = true;
-            }
-            // Send the invoke command to the remote agent.
-            remoteOut.writeInt(CMD_INVOKE);
-            remoteOut.writeUTF(classname);
-            remoteOut.writeUTF(methodname);
-            remoteOut.flush();
-            // Retrieve and report results from the remote agent.
-            if (readAndReportExecutionResult()) {
-                String result = remoteIn.readUTF();
-                return result;
-            }
-        } catch (IOException | RuntimeException ex) {
-            if (!connection.isRunning()) {
-                // The JDI connection is no longer live, shutdown.
-                handleVMExit();
-            } else {
-                debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
-                return "Execution failure: " + ex.getMessage();
-            }
-        } finally {
-            synchronized (STOP_LOCK) {
-                userCodeRunning = false;
-            }
-        }
-        return "";
-    }
-
-    /**
-     * Retrieves the value of a JShell variable.
-     *
-     * @param classname name of the wrapper class holding the variable
-     * @param varname name of the variable
-     * @return the value as a String
-     */
-    @Override
-    public String varValue(String classname, String varname) {
-        try {
-            // Send the variable-value command to the remote agent.
-            remoteOut.writeInt(CMD_VARVALUE);
-            remoteOut.writeUTF(classname);
-            remoteOut.writeUTF(varname);
-            remoteOut.flush();
-            // Retrieve and report results from the remote agent.
-            if (readAndReportResult()) {
-                String result = remoteIn.readUTF();
-                return result;
-            }
-        } catch (EOFException ex) {
-            handleVMExit();
-        } catch (IOException ex) {
-            debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
-            return "Execution failure: " + ex.getMessage();
-        }
-        return "";
-    }
-
-    /**
-     * Adds a path to the remote classpath.
-     *
-     * @param cp the additional path element
-     * @return true if succesful
-     */
-    @Override
-    public boolean addToClasspath(String cp) {
-        try {
-            // Send the classpath addition command to the remote agent.
-            remoteOut.writeInt(CMD_CLASSPATH);
-            remoteOut.writeUTF(cp);
-            remoteOut.flush();
-            // Retrieve and report results from the remote agent.
-            return readAndReportResult();
-        } catch (IOException ex) {
-            throw new InternalError("Classpath addition failed: " + cp, ex);
-        }
-    }
-
-    /**
-     * Redefine the specified classes. Where 'redefine' is, as in JDI and JVMTI,
-     * an in-place replacement of the classes (preserving class identity) --
-     * that is, existing references to the class do not need to be recompiled.
-     * This implementation uses JDI redefineClasses. It will be unsuccessful if
-     * the signature of the class has changed (see the JDI spec). The
-     * JShell-core is designed to adapt to unsuccessful redefine.
-     *
-     * @param classes the names of the classes to redefine
-     * @return true if all the classes were redefined
-     */
-    @Override
-    public boolean redefine(Collection<String> classes) {
-        try {
-            // Create corresponding ClassInfo instances to track the classes.
-            // Each ClassInfo has the current class bytes associated with it.
-            List<ClassInfo> infos = withBytes(classes);
-            // Convert to the JDI ReferenceType to class bytes map form needed
-            // by JDI.
-            Map<ReferenceType, byte[]> rmp = infos.stream()
-                    .collect(toMap(
-                            ci -> ci.getReferenceTypeOrNull(),
-                            ci -> ci.getBytes()));
-            // Attempt redefine.  Throws exceptions on failure.
-            connection.vm().redefineClasses(rmp);
-            // Successful: mark the bytes as loaded.
-            infos.stream()
-                    .forEach(ci -> ci.markLoaded());
-            return true;
-        } catch (UnsupportedOperationException ex) {
-            // A form of class transformation not supported by JDI
-            return false;
-        } catch (Exception ex) {
-            debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex);
-            return false;
-        }
-    }
-
-    // the VM has gone down in flames or because user evaled System.exit() or the like
-    void handleVMExit() {
-        if (connection != null) {
-            // If there is anything left dispose of it
-            connection.disposeVM();
-        }
-        // Tell JShell-core that the VM has died
-        execEnv.closeDown();
-    }
-
-    // Lazy init class tracker
-    private ClassTracker classTracker() {
-        if (classTracker == null) {
-            classTracker = new ClassTracker(connection.vm());
-        }
-        return classTracker;
-    }
-
-    /**
-     * Converts a collection of class names into ClassInfo instances associated
-     * with the most recently compiled class bytes.
-     *
-     * @param classes names of the classes
-     * @return a list of corresponding ClassInfo instances
-     */
-    private List<ClassInfo> withBytes(Collection<String> classes) {
-        return classes.stream()
-                .map(cn -> classTracker().classInfo(cn, execEnv.getClassBytes(cn)))
-                .collect(toList());
-    }
-
-    /**
-     * Reports the status of the named class. UNKNOWN if not loaded. CURRENT if
-     * the most recent successfully loaded/redefined bytes match the current
-     * compiled bytes.
-     *
-     * @param classname the name of the class to test
-     * @return the status
-     */
-    @Override
-    public ClassStatus getClassStatus(String classname) {
-        ClassInfo ci = classTracker().get(classname);
-        if (ci.getReferenceTypeOrNull() == null) {
-            // If the class does not have a JDI ReferenceType it has not been loaded
-            return ClassStatus.UNKNOWN;
-        }
-        // Compare successfully loaded with last compiled bytes.
-        return (Arrays.equals(execEnv.getClassBytes(classname), ci.getLoadedBytes()))
-                ? ClassStatus.CURRENT
-                : ClassStatus.NOT_CURRENT;
-    }
-
-    /**
-     * Reports results from a remote agent command that does not expect
-     * exceptions.
-     *
-     * @return true if successful
-     * @throws IOException if the connection has dropped
-     */
-    private boolean readAndReportResult() throws IOException {
-        int ok = remoteIn.readInt();
-        switch (ok) {
-            case RESULT_SUCCESS:
-                return true;
-            case RESULT_FAIL: {
-                String ex = remoteIn.readUTF();
-                debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
-                return false;
-            }
-            default: {
-                debug(DBG_GEN, "Bad remote result code: %s\n", ok);
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Reports results from a remote agent command that expects runtime
-     * exceptions.
-     *
-     * @return true if successful
-     * @throws IOException if the connection has dropped
-     * @throws EvalException if a user exception was encountered on invoke
-     * @throws UnresolvedReferenceException if an unresolved reference was
-     * encountered
-     */
-    private boolean readAndReportExecutionResult() throws IOException, JShellException {
-        int ok = remoteIn.readInt();
-        switch (ok) {
-            case RESULT_SUCCESS:
-                return true;
-            case RESULT_FAIL: {
-                // An internal error has occurred.
-                String ex = remoteIn.readUTF();
-                return false;
-            }
-            case RESULT_EXCEPTION: {
-                // A user exception was encountered.
-                String exceptionClassName = remoteIn.readUTF();
-                String message = remoteIn.readUTF();
-                StackTraceElement[] elems = readStackTrace();
-                throw execEnv.createEvalException(message, exceptionClassName, elems);
-            }
-            case RESULT_CORRALLED: {
-                // An unresolved reference was encountered.
-                int id = remoteIn.readInt();
-                StackTraceElement[] elems = readStackTrace();
-                throw execEnv.createUnresolvedReferenceException(id, elems);
-            }
-            case RESULT_KILLED: {
-                // Execution was aborted by the stop()
-                debug(DBG_GEN, "Killed.");
-                return false;
-            }
-            default: {
-                debug(DBG_GEN, "Bad remote result code: %s\n", ok);
-                return false;
-            }
-        }
-    }
-
-    private StackTraceElement[] readStackTrace() throws IOException {
-        int elemCount = remoteIn.readInt();
-        StackTraceElement[] elems = new StackTraceElement[elemCount];
-        for (int i = 0; i < elemCount; ++i) {
-            String className = remoteIn.readUTF();
-            String methodName = remoteIn.readUTF();
-            String fileName = remoteIn.readUTF();
-            int line = remoteIn.readInt();
-            elems[i] = new StackTraceElement(className, methodName, fileName, line);
-        }
-        return elems;
-    }
-
-    private final Object STOP_LOCK = new Object();
-    private boolean userCodeRunning = false;
-
-    /**
-     * Interrupt a running invoke.
-     */
-    @Override
-    public void stop() {
-        synchronized (STOP_LOCK) {
-            if (!userCodeRunning) {
-                return;
-            }
-
-            VirtualMachine vm = connection.vm();
-            vm.suspend();
-            try {
-                OUTER:
-                for (ThreadReference thread : vm.allThreads()) {
-                    // could also tag the thread (e.g. using name), to find it easier
-                    for (StackFrame frame : thread.frames()) {
-                        String remoteAgentName = "jdk.internal.jshell.remote.RemoteAgent";
-                        if (remoteAgentName.equals(frame.location().declaringType().name())
-                                && "commandLoop".equals(frame.location().method().name())) {
-                            ObjectReference thiz = frame.thisObject();
-                            if (((BooleanValue) thiz.getValue(thiz.referenceType().fieldByName("inClientCode"))).value()) {
-                                thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(true));
-                                ObjectReference stopInstance = (ObjectReference) thiz.getValue(thiz.referenceType().fieldByName("stopException"));
-
-                                vm.resume();
-                                debug(DBG_GEN, "Attempting to stop the client code...\n");
-                                thread.stop(stopInstance);
-                                thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(false));
-                            }
-
-                            break OUTER;
-                        }
-                    }
-                }
-            } catch (ClassNotLoadedException | IncompatibleThreadStateException | InvalidTypeException ex) {
-                debug(DBG_GEN, "Exception on remote stop: %s\n", ex);
-            } finally {
-                vm.resume();
-            }
-        }
-    }
-
-    void debug(int flags, String format, Object... args) {
-        InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), flags, format, args);
-    }
-
-    void debug(Exception ex, String where) {
-        InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), ex, where);
-    }
-
-    private final class DemultiplexInput extends Thread {
-
-        private final DataInputStream delegate;
-        private final PipeInputStream command;
-        private final PrintStream out;
-        private final PrintStream err;
-
-        public DemultiplexInput(InputStream input,
-                PipeInputStream command,
-                PrintStream out,
-                PrintStream err) {
-            super("output reader");
-            this.delegate = new DataInputStream(input);
-            this.command = command;
-            this.out = out;
-            this.err = err;
-        }
-
-        public void run() {
-            try {
-                while (true) {
-                    int nameLen = delegate.read();
-                    if (nameLen == (-1))
-                        break;
-                    byte[] name = new byte[nameLen];
-                    DemultiplexInput.this.delegate.readFully(name);
-                    int dataLen = delegate.read();
-                    byte[] data = new byte[dataLen];
-                    DemultiplexInput.this.delegate.readFully(data);
-                    switch (new String(name, "UTF-8")) {
-                        case "err":
-                            err.write(data);
-                            break;
-                        case "out":
-                            out.write(data);
-                            break;
-                        case "command":
-                            for (byte b : data) {
-                                command.write(Byte.toUnsignedInt(b));
-                            }
-                            break;
-                    }
-                }
-            } catch (IOException ex) {
-                debug(ex, "Failed reading output");
-            } finally {
-                command.close();
-            }
-        }
-
-    }
-
-    public static final class PipeInputStream extends InputStream {
-        public static final int INITIAL_SIZE = 128;
-
-        private int[] buffer = new int[INITIAL_SIZE];
-        private int start;
-        private int end;
-        private boolean closed;
-
-        @Override
-        public synchronized int read() {
-            while (start == end) {
-                if (closed) {
-                    return -1;
-                }
-                try {
-                    wait();
-                } catch (InterruptedException ex) {
-                    //ignore
-                }
-            }
-            try {
-                return buffer[start];
-            } finally {
-                start = (start + 1) % buffer.length;
-            }
-        }
-
-        public synchronized void write(int b) {
-            if (closed)
-                throw new IllegalStateException("Already closed.");
-            int newEnd = (end + 1) % buffer.length;
-            if (newEnd == start) {
-                //overflow:
-                int[] newBuffer = new int[buffer.length * 2];
-                int rightPart = (end > start ? end : buffer.length) - start;
-                int leftPart = end > start ? 0 : start - 1;
-                System.arraycopy(buffer, start, newBuffer, 0, rightPart);
-                System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart);
-                buffer = newBuffer;
-                start = 0;
-                end = rightPart + leftPart;
-                newEnd = end + 1;
-            }
-            buffer[end] = b;
-            end = newEnd;
-            notifyAll();
-        }
-
-        @Override
-        public synchronized void close() {
-            closed = true;
-            notifyAll();
-        }
-
-    }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDINotConnectedException.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-/*
- * Copyright (c) 1999, 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.internal.jshell.jdi;
-
-/**
- * Internal exception when Java Debug Interface VirtualMacine is not connected.
- * Copy of jdb VMNotConnectedException.
- */
-class JDINotConnectedException extends RuntimeException {
-
-    private static final long serialVersionUID = -7433430494903950165L;
-
-    public JDINotConnectedException() {
-        super();
-    }
-
-    public JDINotConnectedException(String s) {
-        super(s);
-    }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteAgent.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,326 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.internal.jshell.remote;
-import jdk.jshell.spi.SPIResolutionException;
-import java.io.File;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.Socket;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static jdk.internal.jshell.remote.RemoteCodes.*;
-
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * The remote agent runs in the execution process (separate from the main JShell
- * process.  This agent loads code over a socket from the main JShell process,
- * executes the code, and other misc,
- * @author Robert Field
- */
-class RemoteAgent {
-
-    private final RemoteClassLoader loader = new RemoteClassLoader();
-    private final Map<String, Class<?>> klasses = new TreeMap<>();
-
-    public static void main(String[] args) throws Exception {
-        String loopBack = null;
-        Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
-        (new RemoteAgent()).commandLoop(socket);
-    }
-
-    void commandLoop(Socket socket) throws IOException {
-        // in before out -- so we don't hang the controlling process
-        ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
-        OutputStream socketOut = socket.getOutputStream();
-        System.setOut(new PrintStream(new MultiplexingOutputStream("out", socketOut), true));
-        System.setErr(new PrintStream(new MultiplexingOutputStream("err", socketOut), true));
-        ObjectOutputStream out = new ObjectOutputStream(new MultiplexingOutputStream("command", socketOut));
-        while (true) {
-            int cmd = in.readInt();
-            switch (cmd) {
-                case CMD_EXIT:
-                    // Terminate this process
-                    return;
-                case CMD_LOAD:
-                    // Load a generated class file over the wire
-                    try {
-                        int count = in.readInt();
-                        List<String> names = new ArrayList<>(count);
-                        for (int i = 0; i < count; ++i) {
-                            String name = in.readUTF();
-                            byte[] kb = (byte[]) in.readObject();
-                            loader.delare(name, kb);
-                            names.add(name);
-                        }
-                        for (String name : names) {
-                            Class<?> klass = loader.loadClass(name);
-                            klasses.put(name, klass);
-                            // Get class loaded to the point of, at least, preparation
-                            klass.getDeclaredMethods();
-                        }
-                        out.writeInt(RESULT_SUCCESS);
-                        out.flush();
-                    } catch (IOException | ClassNotFoundException | ClassCastException ex) {
-                        debug("*** Load failure: %s\n", ex);
-                        out.writeInt(RESULT_FAIL);
-                        out.writeUTF(ex.toString());
-                        out.flush();
-                    }
-                    break;
-                case CMD_INVOKE: {
-                    // Invoke executable entry point in loaded code
-                    String name = in.readUTF();
-                    Class<?> klass = klasses.get(name);
-                    if (klass == null) {
-                        debug("*** Invoke failure: no such class loaded %s\n", name);
-                        out.writeInt(RESULT_FAIL);
-                        out.writeUTF("no such class loaded: " + name);
-                        out.flush();
-                        break;
-                    }
-                    String methodName = in.readUTF();
-                    Method doitMethod;
-                    try {
-                        this.getClass().getModule().addExports(SPIResolutionException.class.getPackage().getName(), klass.getModule());
-                        doitMethod = klass.getDeclaredMethod(methodName, new Class<?>[0]);
-                        doitMethod.setAccessible(true);
-                        Object res;
-                        try {
-                            clientCodeEnter();
-                            res = doitMethod.invoke(null, new Object[0]);
-                        } catch (InvocationTargetException ex) {
-                            if (ex.getCause() instanceof StopExecutionException) {
-                                expectingStop = false;
-                                throw (StopExecutionException) ex.getCause();
-                            }
-                            throw ex;
-                        } catch (StopExecutionException ex) {
-                            expectingStop = false;
-                            throw ex;
-                        } finally {
-                            clientCodeLeave();
-                        }
-                        out.writeInt(RESULT_SUCCESS);
-                        out.writeUTF(valueString(res));
-                        out.flush();
-                    } catch (InvocationTargetException ex) {
-                        Throwable cause = ex.getCause();
-                        StackTraceElement[] elems = cause.getStackTrace();
-                        if (cause instanceof SPIResolutionException) {
-                            out.writeInt(RESULT_CORRALLED);
-                            out.writeInt(((SPIResolutionException) cause).id());
-                        } else {
-                            out.writeInt(RESULT_EXCEPTION);
-                            out.writeUTF(cause.getClass().getName());
-                            out.writeUTF(cause.getMessage() == null ? "<none>" : cause.getMessage());
-                        }
-                        out.writeInt(elems.length);
-                        for (StackTraceElement ste : elems) {
-                            out.writeUTF(ste.getClassName());
-                            out.writeUTF(ste.getMethodName());
-                            out.writeUTF(ste.getFileName() == null ? "<none>" : ste.getFileName());
-                            out.writeInt(ste.getLineNumber());
-                        }
-                        out.flush();
-                    } catch (NoSuchMethodException | IllegalAccessException ex) {
-                        debug("*** Invoke failure: %s -- %s\n", ex, ex.getCause());
-                        out.writeInt(RESULT_FAIL);
-                        out.writeUTF(ex.toString());
-                        out.flush();
-                    } catch (StopExecutionException ex) {
-                        try {
-                            out.writeInt(RESULT_KILLED);
-                            out.flush();
-                        } catch (IOException err) {
-                            debug("*** Error writing killed result: %s -- %s\n", ex, ex.getCause());
-                        }
-                    }
-                    System.out.flush();
-                    break;
-                }
-                case CMD_VARVALUE: {
-                    // Retrieve a variable value
-                    String classname = in.readUTF();
-                    String varname = in.readUTF();
-                    Class<?> klass = klasses.get(classname);
-                    if (klass == null) {
-                        debug("*** Var value failure: no such class loaded %s\n", classname);
-                        out.writeInt(RESULT_FAIL);
-                        out.writeUTF("no such class loaded: " + classname);
-                        out.flush();
-                        break;
-                    }
-                    try {
-                        Field var = klass.getDeclaredField(varname);
-                        var.setAccessible(true);
-                        Object res = var.get(null);
-                        out.writeInt(RESULT_SUCCESS);
-                        out.writeUTF(valueString(res));
-                        out.flush();
-                    } catch (Exception ex) {
-                        debug("*** Var value failure: no such field %s.%s\n", classname, varname);
-                        out.writeInt(RESULT_FAIL);
-                        out.writeUTF("no such field loaded: " + varname + " in class: " + classname);
-                        out.flush();
-                    }
-                    break;
-                }
-                case CMD_CLASSPATH: {
-                    // Append to the claspath
-                    String cp = in.readUTF();
-                    for (String path : cp.split(File.pathSeparator)) {
-                        loader.addURL(new File(path).toURI().toURL());
-                    }
-                    out.writeInt(RESULT_SUCCESS);
-                    out.flush();
-                    break;
-                }
-                default:
-                    debug("*** Bad command code: %d\n", cmd);
-                    break;
-            }
-        }
-    }
-
-    // These three variables are used by the main JShell process in interrupting
-    // the running process.  Access is via JDI, so the reference is not visible
-    // to code inspection.
-    private boolean inClientCode; // Queried by the main process
-    private boolean expectingStop; // Set by the main process
-
-    // thrown by the main process via JDI:
-    private final StopExecutionException stopException = new StopExecutionException();
-
-    @SuppressWarnings("serial")             // serialVersionUID intentionally omitted
-    private class StopExecutionException extends ThreadDeath {
-        @Override public synchronized Throwable fillInStackTrace() {
-            return this;
-        }
-    }
-
-    void clientCodeEnter() {
-        expectingStop = false;
-        inClientCode = true;
-    }
-
-    void clientCodeLeave() {
-        inClientCode = false;
-        while (expectingStop) {
-            try {
-                Thread.sleep(0);
-            } catch (InterruptedException ex) {
-                debug("*** Sleep interrupted while waiting for stop exception: %s\n", ex);
-            }
-        }
-    }
-
-    private void debug(String format, Object... args) {
-        System.err.printf("REMOTE: "+format, args);
-    }
-
-    static String valueString(Object value) {
-        if (value == null) {
-            return "null";
-        } else if (value instanceof String) {
-            return "\"" + (String)value + "\"";
-        } else if (value instanceof Character) {
-            return "'" + value + "'";
-        } else {
-            return value.toString();
-        }
-    }
-
-    private static final class MultiplexingOutputStream extends OutputStream {
-
-        private static final int PACKET_SIZE = 127;
-
-        private final byte[] name;
-        private final OutputStream delegate;
-
-        public MultiplexingOutputStream(String name, OutputStream delegate) {
-            try {
-                this.name = name.getBytes("UTF-8");
-                this.delegate = delegate;
-            } catch (UnsupportedEncodingException ex) {
-                throw new IllegalStateException(ex); //should not happen
-            }
-        }
-
-        @Override
-        public void write(int b) throws IOException {
-            synchronized (delegate) {
-                delegate.write(name.length); //assuming the len is small enough to fit into byte
-                delegate.write(name);
-                delegate.write(1);
-                delegate.write(b);
-                delegate.flush();
-            }
-        }
-
-        @Override
-        public void write(byte[] b, int off, int len) throws IOException {
-            synchronized (delegate) {
-                int i = 0;
-                while (len > 0) {
-                    int size = Math.min(PACKET_SIZE, len);
-
-                    delegate.write(name.length); //assuming the len is small enough to fit into byte
-                    delegate.write(name);
-                    delegate.write(size);
-                    delegate.write(b, off + i, size);
-                    i += size;
-                    len -= size;
-                }
-
-                delegate.flush();
-            }
-        }
-
-        @Override
-        public void flush() throws IOException {
-            super.flush();
-            delegate.flush();
-        }
-
-        @Override
-        public void close() throws IOException {
-            super.close();
-            delegate.close();
-        }
-
-    }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteClassLoader.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.internal.jshell.remote;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.security.CodeSource;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * Class loader wrapper which caches class files by name until requested.
- * @author Robert Field
- */
-class RemoteClassLoader extends URLClassLoader {
-
-    private final Map<String, byte[]> classObjects = new TreeMap<>();
-
-    RemoteClassLoader() {
-        super(new URL[0]);
-    }
-
-    void delare(String name, byte[] bytes) {
-        classObjects.put(name, bytes);
-    }
-
-    @Override
-    protected Class<?> findClass(String name) throws ClassNotFoundException {
-        byte[] b = classObjects.get(name);
-        if (b == null) {
-            return super.findClass(name);
-        }
-        return super.defineClass(name, b, 0, b.length, (CodeSource) null);
-    }
-
-    @Override
-    public void addURL(URL url) {
-        super.addURL(url);
-    }
-
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteCodes.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- * Copyright (c) 2014, 2015, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.internal.jshell.remote;
-
-/**
- * Communication constants shared between the main process and the remote
- * execution process
- * @author Robert Field
- */
-public class RemoteCodes {
-    // Command codes
-    public static final int CMD_EXIT       = 0;
-    public static final int CMD_LOAD       = 1;
-    public static final int CMD_INVOKE     = 3;
-    public static final int CMD_CLASSPATH  = 4;
-    public static final int CMD_VARVALUE   = 5;
-
-    // Return result codes
-    public static final int RESULT_SUCCESS   = 100;
-    public static final int RESULT_FAIL      = 101;
-    public static final int RESULT_EXCEPTION = 102;
-    public static final int RESULT_CORRALLED = 103;
-    public static final int RESULT_KILLED    = 104;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/ClassTracker.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Objects;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+
+/**
+ * Tracks the state of a class.
+ */
+class ClassTracker {
+
+    private final HashMap<String, ClassInfo> map;
+
+    ClassTracker() {
+        this.map = new HashMap<>();
+    }
+
+    /**
+     * Associates a class name, class bytes (current and loaded).
+     */
+    class ClassInfo {
+
+        // The name of the class
+        private final String className;
+
+        // The corresponding compiled class bytes when a load or redefine
+        // is started.  May not be the loaded bytes.  May be null.
+        private byte[] currentBytes;
+
+        // The class bytes successfully loaded/redefined into the remote VM.
+        private byte[] loadedBytes;
+
+        private ClassInfo(String className) {
+            this.className = className;
+        }
+
+        String getClassName() {
+            return className;
+        }
+
+        byte[] getLoadedBytes() {
+            return loadedBytes;
+        }
+
+        byte[] getCurrentBytes() {
+            return currentBytes;
+        }
+
+        void setCurrentBytes(byte[] bytes) {
+            this.currentBytes = bytes;
+        }
+
+        void setLoadedBytes(byte[] bytes) {
+            this.loadedBytes = bytes;
+        }
+
+        boolean isLoaded() {
+            return loadedBytes != null;
+        }
+
+        boolean isCurrent() {
+            return Arrays.equals(currentBytes, loadedBytes);
+        }
+
+        ClassBytecodes toClassBytecodes() {
+            return new ClassBytecodes(className, currentBytes);
+        }
+
+            @Override
+        public boolean equals(Object o) {
+            return o instanceof ClassInfo
+                    && ((ClassInfo) o).className.equals(className);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(this.className);
+        }
+    }
+
+    void markLoaded(ClassBytecodes[] cbcs) {
+        for (ClassBytecodes cbc : cbcs) {
+            get(cbc.name()).setLoadedBytes(cbc.bytecodes());
+        }
+    }
+
+    void markLoaded(ClassBytecodes[] cbcs, boolean[] isLoaded) {
+        for (int i = 0; i < cbcs.length; ++i) {
+            if (isLoaded[i]) {
+                ClassBytecodes cbc = cbcs[i];
+                get(cbc.name()).setLoadedBytes(cbc.bytecodes());
+            }
+        }
+    }
+
+    // Map a class name to the current compiled class bytes.
+    void setCurrentBytes(String className, byte[] bytes) {
+        ClassInfo ci = get(className);
+        ci.setCurrentBytes(bytes);
+    }
+
+    // Lookup the ClassInfo by class name, create if it does not exist.
+    ClassInfo get(String className) {
+        return map.computeIfAbsent(className, k -> new ClassInfo(k));
+    }
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java	Wed Jul 20 23:19:09 2016 -0700
@@ -61,6 +61,14 @@
 import jdk.jshell.TreeDissector.ExpressionInfo;
 import jdk.jshell.Wrap.Range;
 import jdk.jshell.Snippet.Status;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.InternalException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
+import jdk.jshell.spi.ExecutionControl.ResolutionException;
+import jdk.jshell.spi.ExecutionControl.RunException;
+import jdk.jshell.spi.ExecutionControl.UserException;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
 import static java.util.Collections.singletonList;
@@ -541,15 +549,21 @@
         if (si.status().isDefined()) {
             if (si.isExecutable()) {
                 try {
-                value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
+                    value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
                     value = si.subKind().hasValue()
                             ? expunge(value)
                             : "";
-                } catch (EvalException ex) {
+                } catch (ResolutionException ex) {
+                    DeclarationSnippet sn = (DeclarationSnippet) state.maps.getSnippetDeadOrAlive(ex.id());
+                    exception = new UnresolvedReferenceException(sn, ex.getStackTrace());
+                } catch (UserException ex) {
                     exception = translateExecutionException(ex);
-                } catch (JShellException ex) {
-                    // UnresolvedReferenceException
-                    exception = ex;
+                } catch (RunException ex) {
+                    // StopException - no-op
+                } catch (InternalException ex) {
+                    state.debug(ex, "invoke");
+                } catch (EngineTerminationException ex) {
+                    state.closeDown();
                 }
             } else if (si.subKind() == SubKind.VAR_DECLARATION_SUBKIND) {
                 switch (((VarSnippet) si).typeName()) {
@@ -700,15 +714,25 @@
 
     /**
      * If there are classes to load, loads by calling the execution engine.
-     * @param classnames names of the classes to load.
+     * @param classbytecoes names of the classes to load.
      */
-    private void load(Collection<String> classnames) {
-        if (!classnames.isEmpty()) {
-            state.executionControl().load(classnames);
+    private void load(Collection<ClassBytecodes> classbytecoes) {
+        if (!classbytecoes.isEmpty()) {
+            ClassBytecodes[] cbcs = classbytecoes.toArray(new ClassBytecodes[classbytecoes.size()]);
+            try {
+                state.executionControl().load(cbcs);
+                state.classTracker.markLoaded(cbcs);
+            } catch (ClassInstallException ex) {
+                state.classTracker.markLoaded(cbcs, ex.installed());
+            } catch (NotImplementedException ex) {
+                state.debug(ex, "Seriously?!? load not implemented");
+            } catch (EngineTerminationException ex) {
+                state.closeDown();
+            }
         }
     }
 
-    private EvalException translateExecutionException(EvalException ex) {
+    private EvalException translateExecutionException(UserException ex) {
         StackTraceElement[] raw = ex.getStackTrace();
         int last = raw.length;
         do {
@@ -739,7 +763,7 @@
         if (msg.equals("<none>")) {
             msg = null;
         }
-        return new EvalException(msg, ex.getExceptionClassName(), elems);
+        return new EvalException(msg, ex.causeExceptionClass(), elems);
     }
 
     private boolean isWrap(StackTraceElement ste) {
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java	Wed Jul 20 23:19:09 2016 -0700
@@ -44,13 +44,15 @@
 
 import java.util.function.Supplier;
 import jdk.internal.jshell.debug.InternalDebugControl;
-import jdk.internal.jshell.jdi.FailOverExecutionControl;
+import jdk.jshell.Snippet.Status;
+import jdk.jshell.execution.JDIDefaultExecutionControl;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
+import jdk.jshell.spi.ExecutionEnv;
+import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
 import static java.util.stream.Collectors.collectingAndThen;
 import static java.util.stream.Collectors.toList;
 import static jdk.jshell.Util.expunge;
-import jdk.jshell.Snippet.Status;
-import jdk.internal.jshell.jdi.JDIExecutionControl;
-import jdk.jshell.spi.ExecutionEnv;
 
 /**
  * The JShell evaluation state engine.  This is the central class in the JShell
@@ -90,17 +92,17 @@
     final BiFunction<Snippet, Integer, String> idGenerator;
     final List<String> extraRemoteVMOptions;
     final List<String> extraCompilerOptions;
-    final ExecutionControl executionControl;
+    final ExecutionControl.Generator executionControlGenerator;
 
     private int nextKeyIndex = 1;
 
     final Eval eval;
-    private final Map<String, byte[]> classnameToBytes = new HashMap<>();
+    final ClassTracker classTracker;
     private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>();
     private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>();
     private boolean closed = false;
 
-    private boolean executionControlLaunched = false;
+    private ExecutionControl executionControl = null;
     private SourceCodeAnalysisImpl sourceCodeAnalysis = null;
 
     private static final String L10N_RB_NAME    = "jdk.jshell.resources.l10n";
@@ -114,17 +116,18 @@
         this.idGenerator = b.idGenerator;
         this.extraRemoteVMOptions = b.extraRemoteVMOptions;
         this.extraCompilerOptions = b.extraCompilerOptions;
-        this.executionControl = b.executionControl==null
-                ? new FailOverExecutionControl(
-                        new JDIExecutionControl(),
-                        new JDIExecutionControl(false))
-                : b.executionControl;
+        this.executionControlGenerator = b.executionControlGenerator==null
+                ? failOverExecutionControlGenerator(
+                        JDIDefaultExecutionControl.launch(),
+                        JDIDefaultExecutionControl.listen())
+                : b.executionControlGenerator;
 
         this.maps = new SnippetMaps(this);
         this.keyMap = new KeyMap(this);
         this.outerMap = new OuterWrapMap(this);
         this.taskFactory = new TaskFactory(this);
         this.eval = new Eval(this);
+        this.classTracker = new ClassTracker();
     }
 
     /**
@@ -154,7 +157,7 @@
         BiFunction<Snippet, Integer, String> idGenerator = null;
         List<String> extraRemoteVMOptions = new ArrayList<>();
         List<String> extraCompilerOptions = new ArrayList<>();
-        ExecutionControl executionControl;
+        ExecutionControl.Generator executionControlGenerator;
 
         Builder() { }
 
@@ -310,12 +313,12 @@
          * Sets the custom engine for execution. Snippet execution will be
          * provided by the specified {@link ExecutionControl} instance.
          *
-         * @param execEngine the execution engine
+         * @param executionControlGenerator the execution engine generator
          * @return the {@code Builder} instance (for use in chained
          * initialization)
          */
-        public Builder executionEngine(ExecutionControl execEngine) {
-            this.executionControl = execEngine;
+        public Builder executionEngine(ExecutionControl.Generator executionControlGenerator) {
+            this.executionControlGenerator = executionControlGenerator;
             return this;
         }
 
@@ -397,7 +400,8 @@
      * be an event showing its status changed to OVERWRITTEN, this will not
      * occur for dropped, rejected, or already overwritten declarations.
      * <p>
-     * The execution environment is out of process.  If the evaluated code
+     * If execution environment is out of process, as is the default case, then
+     * if the evaluated code
      * causes the execution environment to terminate, this {@code JShell}
      * instance will be closed but the calling process and VM remain valid.
      * @param input The input String to evaluate
@@ -447,8 +451,14 @@
      * @param path the path to add to the classpath.
      */
     public void addToClasspath(String path) {
-        taskFactory.addToClasspath(path);  // Compiler
-        executionControl().addToClasspath(path);       // Runtime
+        // Compiler
+        taskFactory.addToClasspath(path);
+        // Runtime
+        try {
+            executionControl().addToClasspath(path);
+        } catch (ExecutionControlException ex) {
+            debug(ex, "on addToClasspath(" + path + ")");
+        }
         if (sourceCodeAnalysis != null) {
             sourceCodeAnalysis.classpathChanged();
         }
@@ -468,8 +478,13 @@
      * catching the {@link ThreadDeath} exception.
      */
     public void stop() {
-        if (executionControl != null)
-            executionControl.stop();
+        if (executionControl != null) {
+            try {
+                executionControl.stop();
+            } catch (ExecutionControlException ex) {
+                debug(ex, "on stop()");
+            }
+        }
     }
 
     /**
@@ -622,7 +637,15 @@
             throw new IllegalArgumentException(
                     messageFormat("jshell.exc.var.not.valid",  snippet, snippet.status()));
         }
-        String value = executionControl().varValue(snippet.classFullName(), snippet.name());
+        String value;
+        try {
+            value = executionControl().varValue(snippet.classFullName(), snippet.name());
+        } catch (EngineTerminationException ex) {
+            throw new IllegalStateException(ex.getMessage());
+        } catch (ExecutionControlException ex) {
+            debug(ex, "In varValue()");
+            return "[" + ex.getMessage() + "]";
+        }
         return expunge(value);
     }
 
@@ -696,43 +719,22 @@
         }
 
         @Override
-        public JShell state() {
-            return JShell.this;
-        }
-
-        @Override
         public List<String> extraRemoteVMOptions() {
             return extraRemoteVMOptions;
         }
 
         @Override
-        public byte[] getClassBytes(String classname) {
-            return classnameToBytes.get(classname);
-        }
-
-        @Override
-        public EvalException createEvalException(String message, String exceptionClass, StackTraceElement[] stackElements) {
-            return new EvalException(message, exceptionClass, stackElements);
-        }
-
-        @Override
-        public UnresolvedReferenceException createUnresolvedReferenceException(int id, StackTraceElement[] stackElements) {
-            DeclarationSnippet sn = (DeclarationSnippet) maps.getSnippetDeadOrAlive(id);
-            return new UnresolvedReferenceException(sn, stackElements);
-        }
-
-        @Override
         public void closeDown() {
             JShell.this.closeDown();
         }
+
     }
 
     // --- private / package-private implementation support ---
     ExecutionControl executionControl() {
-        if (!executionControlLaunched) {
+        if (executionControl == null) {
             try {
-                executionControlLaunched = true;
-                executionControl.start(new ExecutionEnvImpl());
+                executionControl =  executionControlGenerator.generate(new ExecutionEnvImpl());
             } catch (Throwable ex) {
                 throw new InternalError("Launching execution engine threw: " + ex.getMessage(), ex);
             }
@@ -740,10 +742,6 @@
         return executionControl;
     }
 
-    void setClassnameToBytes(String classname, byte[] bytes) {
-        classnameToBytes.put(classname, bytes);
-    }
-
     void debug(int flags, String format, Object... args) {
         InternalDebugControl.debug(this, err, flags, format, args);
     }
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java	Wed Jul 20 23:19:09 2016 -0700
@@ -223,7 +223,6 @@
                     new WrapSourceHandler(),
                     Util.join(new String[] {
                         "-Xshouldstop:at=FLOW", "-Xlint:unchecked",
-                        "-XaddExports:jdk.jshell/jdk.internal.jshell.remote=ALL-UNNAMED",
                         "-proc:none"
                     }, extraArgs));
         }
@@ -267,7 +266,7 @@
 
         CompileTask(final Collection<OuterWrap> wraps) {
             super(wraps.stream(), new WrapSourceHandler(),
-                    "-Xlint:unchecked", "-XaddExports:jdk.jshell/jdk.internal.jshell.remote=ALL-UNNAMED", "-proc:none", "-parameters");
+                    "-Xlint:unchecked", "-proc:none", "-parameters");
         }
 
         boolean compile() {
@@ -286,7 +285,7 @@
             }
             List<String> list = new ArrayList<>();
             for (OutputMemoryJavaFileObject fo : l) {
-                state.setClassnameToBytes(fo.getName(), fo.getBytes());
+                state.classTracker.setCurrentBytes(fo.getName(), fo.getBytes());
                 list.add(fo.getName());
             }
             return list;
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java	Wed Jul 20 23:19:09 2016 -0700
@@ -32,11 +32,16 @@
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Stream;
+import jdk.jshell.ClassTracker.ClassInfo;
 import jdk.jshell.Snippet.Kind;
 import jdk.jshell.Snippet.Status;
 import jdk.jshell.Snippet.SubKind;
 import jdk.jshell.TaskFactory.AnalyzeTask;
 import jdk.jshell.TaskFactory.CompileTask;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
 import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
@@ -75,7 +80,7 @@
     private SnippetEvent replaceOldEvent;
     private List<SnippetEvent> secondaryEvents;
     private boolean isAttemptingCorral;
-    private List<String> toRedefine;
+    private List<ClassInfo> toRedefine;
     private boolean dependenciesNeeded;
 
     Unit(JShell state, Snippet si, Snippet causalSnippet,
@@ -261,30 +266,29 @@
     }
 
     /**
-     * Process the class information from the last compile.
-     * Requires loading of returned list.
+     * Process the class information from the last compile. Requires loading of
+     * returned list.
+     *
      * @return the list of classes to load
      */
-    Stream<String> classesToLoad(List<String> classnames) {
+    Stream<ClassBytecodes> classesToLoad(List<String> classnames) {
         toRedefine = new ArrayList<>();
-        List<String> toLoad = new ArrayList<>();
+        List<ClassBytecodes> toLoad = new ArrayList<>();
         if (status.isDefined() && !isImport()) {
             // Classes should only be loaded/redefined if the compile left them
             // in a defined state.  Imports do not have code and are not loaded.
             for (String cn : classnames) {
-                switch (state.executionControl().getClassStatus(cn)) {
-                    case UNKNOWN:
-                        // If not loaded, add to the list of classes to load.
-                        toLoad.add(cn);
-                        dependenciesNeeded = true;
-                        break;
-                    case NOT_CURRENT:
-                        // If loaded but out of date, add to the list of classes to attempt redefine.
-                        toRedefine.add(cn);
-                        break;
-                    case CURRENT:
-                        // Loaded and current, so nothing to do
-                        break;
+                ClassInfo ci = state.classTracker.get(cn);
+                if (ci.isLoaded()) {
+                    if (ci.isCurrent()) {
+                        // nothing to do
+                    } else {
+                        toRedefine.add(ci);
+                    }
+                } else {
+                    // If not loaded, add to the list of classes to load.
+                    toLoad.add(ci.toClassBytecodes());
+                    dependenciesNeeded = true;
                 }
             }
         }
@@ -292,14 +296,30 @@
     }
 
     /**
-     * Redefine classes needing redefine.
-     * classesToLoad() must be called first.
+     * Redefine classes needing redefine. classesToLoad() must be called first.
+     *
      * @return true if all redefines succeeded (can be vacuously true)
      */
     boolean doRedefines() {
-        return toRedefine.isEmpty()
-                ? true
-                : state.executionControl().redefine(toRedefine);
+        if (toRedefine.isEmpty()) {
+            return true;
+        }
+        ClassBytecodes[] cbcs = toRedefine.stream()
+                .map(ci -> ci.toClassBytecodes())
+                .toArray(size -> new ClassBytecodes[size]);
+        try {
+            state.executionControl().redefine(cbcs);
+            state.classTracker.markLoaded(cbcs);
+            return true;
+        } catch (ClassInstallException ex) {
+            state.classTracker.markLoaded(cbcs, ex.installed());
+            return false;
+        } catch (EngineTerminationException ex) {
+            state.closeDown();
+            return false;
+        } catch (NotImplementedException ex) {
+            return false;
+        }
     }
 
     void markForReplacement() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DefaultLoaderDelegate.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.util.Map;
+import java.util.TreeMap;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.InternalException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
+
+/**
+ * The standard implementation of {@link LoaderDelegate} using
+ * a {@link URLClassLoader}.
+ *
+ * @author Robert Field
+ */
+class DefaultLoaderDelegate implements LoaderDelegate {
+
+    private final RemoteClassLoader loader;
+    private final Map<String, Class<?>> klasses = new TreeMap<>();
+
+    class RemoteClassLoader extends URLClassLoader {
+
+        private final Map<String, byte[]> classObjects = new TreeMap<>();
+
+        RemoteClassLoader() {
+            super(new URL[0]);
+        }
+
+        void delare(String name, byte[] bytes) {
+            classObjects.put(name, bytes);
+        }
+
+        @Override
+        protected Class<?> findClass(String name) throws ClassNotFoundException {
+            byte[] b = classObjects.get(name);
+            if (b == null) {
+                return super.findClass(name);
+            }
+            return super.defineClass(name, b, 0, b.length, (CodeSource) null);
+        }
+
+        @Override
+        public void addURL(URL url) {
+            super.addURL(url);
+        }
+
+    }
+
+    public DefaultLoaderDelegate() {
+        this.loader = new RemoteClassLoader();
+    }
+
+    @Override
+    public void load(ClassBytecodes[] cbcs)
+            throws ClassInstallException, EngineTerminationException {
+        boolean[] loaded = new boolean[cbcs.length];
+        try {
+            for (ClassBytecodes cbc : cbcs) {
+                loader.delare(cbc.name(), cbc.bytecodes());
+            }
+            for (int i = 0; i < cbcs.length; ++i) {
+                ClassBytecodes cbc = cbcs[i];
+                Class<?> klass = loader.loadClass(cbc.name());
+                klasses.put(cbc.name(), klass);
+                loaded[i] = true;
+                // Get class loaded to the point of, at least, preparation
+                klass.getDeclaredMethods();
+            }
+        } catch (Throwable ex) {
+            throw new ClassInstallException("load: " + ex.getMessage(), loaded);
+        }
+    }
+
+
+    @Override
+    public void addToClasspath(String cp)
+            throws EngineTerminationException, InternalException {
+        try {
+            for (String path : cp.split(File.pathSeparator)) {
+                loader.addURL(new File(path).toURI().toURL());
+            }
+        } catch (Exception ex) {
+            throw new InternalException(ex.toString());
+        }
+    }
+
+    @Override
+    public void setClasspath(String path)
+            throws EngineTerminationException, InternalException {
+        throw new NotImplementedException("setClasspath: Not supported yet.");
+    }
+
+    @Override
+    public Class<?> findClass(String name) throws ClassNotFoundException {
+        Class<?> klass = klasses.get(name);
+        if (klass == null) {
+            throw new ClassNotFoundException(name + " not found");
+        } else {
+            return klass;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DemultiplexInput.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+
+/**
+ * Read from an InputStream which has been packetized and write its contents
+ * to the named OutputStreams.
+ *
+ * @author Jan Lahoda
+ * @see Util#demultiplexInput(java.io.InputStream, java.io.OutputStream, java.io.OutputStream, java.io.OutputStream...)
+ */
+class DemultiplexInput extends Thread {
+
+    private final DataInputStream delegate;
+    private final PipeInputStream command;
+    private final Map<String, OutputStream> io;
+
+    DemultiplexInput(InputStream input, PipeInputStream command,
+            Map<String, OutputStream> io) {
+        super("output reader");
+        this.delegate = new DataInputStream(input);
+        this.command = command;
+        this.io = io;
+    }
+
+    @Override
+    public void run() {
+        try {
+            while (true) {
+                int nameLen = delegate.read();
+                if (nameLen == (-1)) {
+                    break;
+                }
+                byte[] name = new byte[nameLen];
+                DemultiplexInput.this.delegate.readFully(name);
+                int dataLen = delegate.read();
+                byte[] data = new byte[dataLen];
+                DemultiplexInput.this.delegate.readFully(data);
+                String chan = new String(name, "UTF-8");
+                if (chan.equals("command")) {
+                    for (byte b : data) {
+                        command.write(Byte.toUnsignedInt(b));
+                    }
+                } else {
+                    OutputStream out = io.get(chan);
+                    if (out == null) {
+                        debug("Unexpected channel name: %s", chan);
+                    } else {
+                        out.write(data);
+                    }
+                }
+            }
+        } catch (IOException ex) {
+            debug(ex, "Failed reading output");
+        } finally {
+            command.close();
+        }
+    }
+
+    /**
+     * Log debugging information. Arguments as for {@code printf}.
+     *
+     * @param format a format string as described in Format string syntax
+     * @param args arguments referenced by the format specifiers in the format
+     * string.
+     */
+    private void debug(String format, Object... args) {
+        // Reserved for future logging
+    }
+
+    /**
+     * Log a serious unexpected internal exception.
+     *
+     * @param ex the exception
+     * @param where a description of the context of the exception
+     */
+    private void debug(Throwable ex, String where) {
+        // Reserved for future logging
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DirectExecutionControl.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.SPIResolutionException;
+
+/**
+ * An {@link ExecutionControl} implementation that runs in the current process.
+ * May be used directly, or over a channel with
+ * {@link Util#forwardExecutionControl(ExecutionControl, java.io.ObjectInput, java.io.ObjectOutput) }.
+ *
+ * @author Robert Field
+ * @author Jan Lahoda
+ */
+public class DirectExecutionControl implements ExecutionControl {
+
+    private final LoaderDelegate loaderDelegate;
+
+    /**
+     * Creates an instance, delegating loader operations to the specified
+     * delegate.
+     *
+     * @param loaderDelegate the delegate to handle loading classes
+     */
+    public DirectExecutionControl(LoaderDelegate loaderDelegate) {
+        this.loaderDelegate = loaderDelegate;
+    }
+
+    /**
+     * Create an instance using the default class loading.
+     */
+    public DirectExecutionControl() {
+        this(new DefaultLoaderDelegate());
+    }
+
+    @Override
+    public void load(ClassBytecodes[] cbcs)
+            throws ClassInstallException, NotImplementedException, EngineTerminationException {
+        loaderDelegate.load(cbcs);
+    }
+
+    @Override
+    public void redefine(ClassBytecodes[] cbcs)
+            throws ClassInstallException, NotImplementedException, EngineTerminationException {
+        throw new NotImplementedException("redefine not supported");
+    }
+
+    @Override
+    public String invoke(String className, String methodName)
+            throws RunException, InternalException, EngineTerminationException {
+        Method doitMethod;
+        try {
+            Class<?> klass = findClass(className);
+            doitMethod = klass.getDeclaredMethod(methodName, new Class<?>[0]);
+            doitMethod.setAccessible(true);
+        } catch (Throwable ex) {
+            throw new InternalException(ex.toString());
+        }
+
+        try {
+            clientCodeEnter();
+            String result = invoke(doitMethod);
+            System.out.flush();
+            return result;
+        } catch (RunException | InternalException | EngineTerminationException ex) {
+            throw ex;
+        } catch (SPIResolutionException ex) {
+            return throwConvertedInvocationException(ex);
+        } catch (InvocationTargetException ex) {
+            return throwConvertedInvocationException(ex.getCause());
+        } catch (Throwable ex) {
+            return throwConvertedOtherException(ex);
+        } finally {
+            clientCodeLeave();
+        }
+    }
+
+    @Override
+    public String varValue(String className, String varName)
+            throws RunException, EngineTerminationException, InternalException {
+        Object val;
+        try {
+            Class<?> klass = findClass(className);
+            Field var = klass.getDeclaredField(varName);
+            var.setAccessible(true);
+            val = var.get(null);
+        } catch (Throwable ex) {
+            throw new InternalException(ex.toString());
+        }
+
+        try {
+            clientCodeEnter();
+            return valueString(val);
+        } catch (Throwable ex) {
+            return throwConvertedInvocationException(ex);
+        } finally {
+            clientCodeLeave();
+        }
+    }
+
+    @Override
+    public void addToClasspath(String cp)
+            throws EngineTerminationException, InternalException {
+        loaderDelegate.addToClasspath(cp);
+    }
+
+    @Override
+    public void setClasspath(String path)
+            throws EngineTerminationException, InternalException {
+        loaderDelegate.setClasspath(path);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Not supported.
+     */
+    @Override
+    public void stop()
+            throws EngineTerminationException, InternalException {
+        throw new NotImplementedException("stop: Not supported.");
+    }
+
+    @Override
+    public Object extensionCommand(String command, Object arg)
+            throws RunException, EngineTerminationException, InternalException {
+        throw new NotImplementedException("Unknown command: " + command);
+    }
+
+    @Override
+    public void close() {
+    }
+
+    /**
+     * Finds the class with the specified binary name.
+     *
+     * @param name the binary name of the class
+     * @return the Class Object
+     * @throws ClassNotFoundException if the class could not be found
+     */
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        return loaderDelegate.findClass(name);
+    }
+
+    /**
+     * Invoke the specified "doit-method", a static method with no parameters.
+     * The {@link DirectExecutionControl#invoke(java.lang.String, java.lang.String) }
+     * in this class will call this to invoke.
+     *
+     * @param doitMethod the Method to invoke
+     * @return the value or null
+     * @throws Exception any exceptions thrown by
+     * {@link java.lang.reflect.Method#invoke(Object, Object...) }
+     * or any {@link ExecutionControl.ExecutionControlException}
+     * to pass-through.
+     */
+    protected String invoke(Method doitMethod) throws Exception {
+        Object res = doitMethod.invoke(null, new Object[0]);
+        return valueString(res);
+    }
+
+    /**
+     * Converts the {@code Object} value from
+     * {@link ExecutionControl#invoke(String, String)  } or
+     * {@link ExecutionControl#varValue(String, String)   } to {@code String}.
+     *
+     * @param value the value to convert
+     * @return the {@code String} representation
+     */
+    protected static String valueString(Object value) {
+        if (value == null) {
+            return "null";
+        } else if (value instanceof String) {
+            return "\"" + (String) value + "\"";
+        } else if (value instanceof Character) {
+            return "'" + value + "'";
+        } else {
+            return value.toString();
+        }
+    }
+
+    /**
+     * Converts incoming exceptions in user code into instances of subtypes of
+     * {@link ExecutionControl.ExecutionControlException} and throws the
+     * converted exception.
+     *
+     * @param cause the exception to convert
+     * @return never returns as it always throws
+     * @throws ExecutionControl.RunException for normal exception occurrences
+     * @throws ExecutionControl.InternalException for internal problems
+     */
+    protected String throwConvertedInvocationException(Throwable cause) throws RunException, InternalException {
+        if (cause instanceof SPIResolutionException) {
+            SPIResolutionException spire = (SPIResolutionException) cause;
+            throw new ResolutionException(spire.id(), spire.getStackTrace());
+        } else {
+            throw new UserException(cause.getMessage(), cause.getClass().getName(), cause.getStackTrace());
+        }
+    }
+
+    /**
+     * Converts incoming exceptions in agent code into instances of subtypes of
+     * {@link ExecutionControl.ExecutionControlException} and throws the
+     * converted exception.
+     *
+     * @param ex the exception to convert
+     * @return never returns as it always throws
+     * @throws ExecutionControl.RunException for normal exception occurrences
+     * @throws ExecutionControl.InternalException for internal problems
+     */
+    protected String throwConvertedOtherException(Throwable ex) throws RunException, InternalException {
+        throw new InternalException(ex.toString());
+    }
+
+    /**
+     * Marks entry into user code.
+     *
+     * @throws ExecutionControl.InternalException in unexpected failure cases
+     */
+    protected void clientCodeEnter() throws InternalException {
+    }
+
+    /**
+     * Marks departure from user code.
+     *
+     * @throws ExecutionControl.InternalException in unexpected failure cases
+     */
+    protected void clientCodeLeave() throws InternalException {
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/ExecutionControlForwarder.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.InternalException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
+import jdk.jshell.spi.ExecutionControl.ResolutionException;
+import jdk.jshell.spi.ExecutionControl.StoppedException;
+import jdk.jshell.spi.ExecutionControl.UserException;
+import static jdk.jshell.execution.RemoteCodes.*;
+
+/**
+ * Forwards commands from the input to the specified {@link ExecutionControl}
+ * instance, then responses back on the output.
+ */
+class ExecutionControlForwarder {
+
+    private final ExecutionControl ec;
+    private final ObjectInput in;
+    private final ObjectOutput out;
+
+    ExecutionControlForwarder(ExecutionControl ec, ObjectInput in, ObjectOutput out) {
+        this.ec = ec;
+        this.in = in;
+        this.out = out;
+    }
+
+    private boolean writeSuccess() throws IOException {
+        writeStatus(RESULT_SUCCESS);
+        flush();
+        return true;
+    }
+
+    private boolean writeSuccessAndResult(String result) throws IOException {
+        writeStatus(RESULT_SUCCESS);
+        writeUTF(result);
+        flush();
+        return true;
+    }
+
+    private boolean writeSuccessAndResult(Object result) throws IOException {
+        writeStatus(RESULT_SUCCESS);
+        writeObject(result);
+        flush();
+        return true;
+    }
+
+    private void writeStatus(int status) throws IOException {
+        out.writeInt(status);
+    }
+
+    private void writeObject(Object o) throws IOException {
+        out.writeObject(o);
+    }
+
+    private void writeInt(int i) throws IOException {
+        out.writeInt(i);
+    }
+
+    private void writeUTF(String s) throws IOException {
+        if (s == null) {
+            s = "";
+        }
+        out.writeUTF(s);
+    }
+
+    private void flush() throws IOException {
+        out.flush();
+    }
+
+    private boolean processCommand() throws IOException {
+        try {
+            int prefix = in.readInt();
+            if (prefix != COMMAND_PREFIX) {
+                throw new EngineTerminationException("Invalid command prefix: " + prefix);
+            }
+            String cmd = in.readUTF();
+            switch (cmd) {
+                case CMD_LOAD: {
+                    // Load a generated class file over the wire
+                    ClassBytecodes[] cbcs = (ClassBytecodes[]) in.readObject();
+                    ec.load(cbcs);
+                    return writeSuccess();
+                }
+                case CMD_REDEFINE: {
+                    // Load a generated class file over the wire
+                    ClassBytecodes[] cbcs = (ClassBytecodes[]) in.readObject();
+                    ec.redefine(cbcs);
+                    return writeSuccess();
+                }
+                case CMD_INVOKE: {
+                    // Invoke executable entry point in loaded code
+                    String className = in.readUTF();
+                    String methodName = in.readUTF();
+                    String res = ec.invoke(className, methodName);
+                    return writeSuccessAndResult(res);
+                }
+                case CMD_VAR_VALUE: {
+                    // Retrieve a variable value
+                    String className = in.readUTF();
+                    String varName = in.readUTF();
+                    String res = ec.varValue(className, varName);
+                    return writeSuccessAndResult(res);
+                }
+                case CMD_ADD_CLASSPATH: {
+                    // Append to the claspath
+                    String cp = in.readUTF();
+                    ec.addToClasspath(cp);
+                    return writeSuccess();
+                }
+                case CMD_SET_CLASSPATH: {
+                    // Set the claspath
+                    String cp = in.readUTF();
+                    ec.setClasspath(cp);
+                    return writeSuccess();
+                }
+                case CMD_STOP: {
+                    // Stop the current execution
+                    try {
+                        ec.stop();
+                    } catch (Throwable ex) {
+                        // JShell-core not waiting for a result, ignore
+                    }
+                    return true;
+                }
+                case CMD_CLOSE: {
+                    // Terminate this process
+                    try {
+                        ec.close();
+                    } catch (Throwable ex) {
+                        // JShell-core not waiting for a result, ignore
+                    }
+                    return true;
+                }
+                default: {
+                    Object arg = in.readObject();
+                    Object res = ec.extensionCommand(cmd, arg);
+                    return writeSuccessAndResult(res);
+                }
+            }
+        } catch (IOException ex) {
+            // handled by the outer level
+            throw ex;
+        } catch (EngineTerminationException ex) {
+            writeStatus(RESULT_TERMINATED);
+            writeUTF(ex.getMessage());
+            flush();
+            return false;
+        } catch (NotImplementedException ex) {
+            writeStatus(RESULT_NOT_IMPLEMENTED);
+            writeUTF(ex.getMessage());
+            flush();
+            return true;
+        } catch (InternalException ex) {
+            writeStatus(RESULT_INTERNAL_PROBLEM);
+            writeUTF(ex.getMessage());
+            flush();
+            return true;
+        } catch (ClassInstallException ex) {
+            writeStatus(RESULT_CLASS_INSTALL_EXCEPTION);
+            writeUTF(ex.getMessage());
+            writeObject(ex.installed());
+            flush();
+            return true;
+        } catch (UserException ex) {
+            writeStatus(RESULT_USER_EXCEPTION);
+            writeUTF(ex.getMessage());
+            writeUTF(ex.causeExceptionClass());
+            writeObject(ex.getStackTrace());
+            flush();
+            return true;
+        } catch (ResolutionException ex) {
+            writeStatus(RESULT_CORRALLED);
+            writeInt(ex.id());
+            writeObject(ex.getStackTrace());
+            flush();
+            return true;
+        } catch (StoppedException ex) {
+            writeStatus(RESULT_STOPPED);
+            flush();
+            return true;
+        } catch (Throwable ex) {
+            writeStatus(RESULT_TERMINATED);
+            writeUTF(ex.getMessage());
+            flush();
+            return false;
+        }
+    }
+
+    void commandLoop() {
+        try {
+            while (processCommand()) {
+                // condition is loop action
+            }
+        } catch (IOException ex) {
+            // drop out of loop
+        }
+    }
+
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Internal.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2016, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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 jdk.jshell.execution;
-
-/**
- * Temp class needed so the package exists.
- */
-class Internal {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIDefaultExecutionControl.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import com.sun.jdi.BooleanValue;
+import com.sun.jdi.ClassNotLoadedException;
+import com.sun.jdi.Field;
+import com.sun.jdi.IncompatibleThreadStateException;
+import com.sun.jdi.InvalidTypeException;
+import com.sun.jdi.ObjectReference;
+import com.sun.jdi.StackFrame;
+import com.sun.jdi.ThreadReference;
+import com.sun.jdi.VMDisconnectedException;
+import com.sun.jdi.VirtualMachine;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionEnv;
+import static jdk.jshell.execution.Util.remoteInput;
+
+/**
+ * The implementation of {@link jdk.jshell.spi.ExecutionControl} that the
+ * JShell-core uses by default.
+ * Launches a remote process -- the "remote agent".
+ * Interfaces to the remote agent over a socket and via JDI.
+ * Designed to work with {@link RemoteExecutionControl}.
+ *
+ * @author Robert Field
+ * @author Jan Lahoda
+ */
+public class JDIDefaultExecutionControl extends JDIExecutionControl {
+
+    private static final String REMOTE_AGENT = RemoteExecutionControl.class.getName();
+
+    private VirtualMachine vm;
+    private Process process;
+
+    private final Object STOP_LOCK = new Object();
+    private boolean userCodeRunning = false;
+
+    /**
+     * Creates an ExecutionControl instance based on a JDI
+     * {@code LaunchingConnector}.
+     *
+     * @return the generator
+     */
+    public static ExecutionControl.Generator launch() {
+        return env -> create(env, true);
+    }
+
+    /**
+     * Creates an ExecutionControl instance based on a JDI
+     * {@code ListeningConnector}.
+     *
+     * @return the generator
+     */
+    public static ExecutionControl.Generator listen() {
+        return env -> create(env, false);
+    }
+
+    /**
+     * Creates an ExecutionControl instance based on a JDI
+     * {@code ListeningConnector} or {@code LaunchingConnector}.
+     *
+     * Initialize JDI and use it to launch the remote JVM. Set-up a socket for
+     * commands and results. This socket also transports the user
+     * input/output/error.
+     *
+     * @param env the context passed by
+     * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }
+     * @return the channel
+     * @throws IOException if there are errors in set-up
+     */
+    private static JDIDefaultExecutionControl create(ExecutionEnv env, boolean isLaunch) throws IOException {
+        try (final ServerSocket listener = new ServerSocket(0)) {
+            // timeout after 60 seconds
+            listener.setSoTimeout(60000);
+            int port = listener.getLocalPort();
+
+            // Set-up the JDI connection
+            JDIInitiator jdii = new JDIInitiator(port,
+                    env.extraRemoteVMOptions(), REMOTE_AGENT, isLaunch);
+            VirtualMachine vm = jdii.vm();
+            Process process = jdii.process();
+
+            // Forward input to the remote agent
+            Util.forwardInputToRemote(env.userIn(), process.getOutputStream(),
+                    ex -> debug(ex, "input forwarding failure"));
+
+            List<Consumer<String>> deathListeners = new ArrayList<>();
+            deathListeners.add(s -> env.closeDown());
+            Util.detectJDIExitEvent(vm, s -> {
+                for (Consumer<String> h : deathListeners) {
+                    h.accept(s);
+                }
+            });
+
+            // Set-up the commands/reslts on the socket.  Piggy-back snippet
+            // output.
+            Socket socket = listener.accept();
+            // out before in -- match remote creation so we don't hang
+            ObjectOutput cmdout = new ObjectOutputStream(socket.getOutputStream());
+            Map<String, OutputStream> io = new HashMap<>();
+            io.put("out", env.userOut());
+            io.put("err", env.userErr());
+            ObjectInput cmdin = remoteInput(socket.getInputStream(), io);
+            return new JDIDefaultExecutionControl(cmdout, cmdin, vm, process, deathListeners);
+        }
+    }
+
+    /**
+     * Create an instance.
+     *
+     * @param cmdout the output for commands
+     * @param cmdin the input for responses
+     */
+    private JDIDefaultExecutionControl(ObjectOutput cmdout, ObjectInput cmdin,
+            VirtualMachine vm, Process process, List<Consumer<String>> deathListeners) {
+        super(cmdout, cmdin);
+        this.vm = vm;
+        this.process = process;
+        deathListeners.add(s -> disposeVM());
+    }
+
+    @Override
+    public String invoke(String classname, String methodname)
+            throws RunException,
+            EngineTerminationException, InternalException {
+        String res;
+        synchronized (STOP_LOCK) {
+            userCodeRunning = true;
+        }
+        try {
+            res = super.invoke(classname, methodname);
+        } finally {
+            synchronized (STOP_LOCK) {
+                userCodeRunning = false;
+            }
+        }
+        return res;
+    }
+
+    /**
+     * Interrupts a running remote invoke by manipulating remote variables
+     * and sending a stop via JDI.
+     *
+     * @throws EngineTerminationException the execution engine has terminated
+     * @throws InternalException an internal problem occurred
+     */
+    @Override
+    public void stop() throws EngineTerminationException, InternalException {
+        synchronized (STOP_LOCK) {
+            if (!userCodeRunning) {
+                return;
+            }
+
+            vm().suspend();
+            try {
+                OUTER:
+                for (ThreadReference thread : vm().allThreads()) {
+                    // could also tag the thread (e.g. using name), to find it easier
+                    for (StackFrame frame : thread.frames()) {
+                        if (REMOTE_AGENT.equals(frame.location().declaringType().name()) &&
+                                (    "invoke".equals(frame.location().method().name())
+                                || "varValue".equals(frame.location().method().name()))) {
+                            ObjectReference thiz = frame.thisObject();
+                            Field inClientCode = thiz.referenceType().fieldByName("inClientCode");
+                            Field expectingStop = thiz.referenceType().fieldByName("expectingStop");
+                            Field stopException = thiz.referenceType().fieldByName("stopException");
+                            if (((BooleanValue) thiz.getValue(inClientCode)).value()) {
+                                thiz.setValue(expectingStop, vm().mirrorOf(true));
+                                ObjectReference stopInstance = (ObjectReference) thiz.getValue(stopException);
+
+                                vm().resume();
+                                debug("Attempting to stop the client code...\n");
+                                thread.stop(stopInstance);
+                                thiz.setValue(expectingStop, vm().mirrorOf(false));
+                            }
+
+                            break OUTER;
+                        }
+                    }
+                }
+            } catch (ClassNotLoadedException | IncompatibleThreadStateException | InvalidTypeException ex) {
+                throw new InternalException("Exception on remote stop: " + ex);
+            } finally {
+                vm().resume();
+            }
+        }
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        disposeVM();
+    }
+
+    private synchronized void disposeVM() {
+        try {
+            if (vm != null) {
+                vm.dispose(); // This could NPE, so it is caught below
+                vm = null;
+            }
+        } catch (VMDisconnectedException ex) {
+            // Ignore if already closed
+        } catch (Throwable ex) {
+            debug(ex, "disposeVM");
+        } finally {
+            if (process != null) {
+                process.destroy();
+                process = null;
+            }
+        }
+    }
+
+    @Override
+    protected synchronized VirtualMachine vm() throws EngineTerminationException {
+        if (vm == null) {
+            throw new EngineTerminationException("VM closed");
+        } else {
+            return vm;
+        }
+    }
+
+    /**
+     * Log debugging information. Arguments as for {@code printf}.
+     *
+     * @param format a format string as described in Format string syntax
+     * @param args arguments referenced by the format specifiers in the format
+     * string.
+     */
+    private static void debug(String format, Object... args) {
+        // Reserved for future logging
+    }
+
+    /**
+     * Log a serious unexpected internal exception.
+     *
+     * @param ex the exception
+     * @param where a description of the context of the exception
+     */
+    private static void debug(Throwable ex, String where) {
+        // Reserved for future logging
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIEventHandler.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 1998, 2014, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.util.function.Consumer;
+import com.sun.jdi.*;
+import com.sun.jdi.event.*;
+
+/**
+ * Handler of Java Debug Interface events.
+ * Adapted from jdb EventHandler.
+ * Only exit and disconnect events processed.
+ */
+class JDIEventHandler implements Runnable {
+
+    private final Thread thread;
+    private volatile boolean connected = true;
+    private boolean completed = false;
+    private final VirtualMachine vm;
+    private final Consumer<String> reportVMExit;
+
+    /**
+     * Creates an event handler. Start with {@code start()}.
+     *
+     * @param vm the virtual machine for which to handle events
+     * @param reportVMExit callback to report exit/disconnect
+     * (passed true if the VM has died)
+     */
+    JDIEventHandler(VirtualMachine vm, Consumer<String> reportVMExit) {
+        this.vm = vm;
+        this.reportVMExit = reportVMExit;
+        this.thread = new Thread(this, "event-handler");
+    }
+
+    /**
+     * Starts the event handler.
+     */
+    void start() {
+        thread.start();
+    }
+
+    synchronized void shutdown() {
+        connected = false;  // force run() loop termination
+        thread.interrupt();
+        while (!completed) {
+            try {wait();} catch (InterruptedException exc) {}
+        }
+    }
+
+    @Override
+    public void run() {
+        EventQueue queue = vm.eventQueue();
+        while (connected) {
+            try {
+                EventSet eventSet = queue.remove();
+                boolean resumeStoppedApp = false;
+                EventIterator it = eventSet.eventIterator();
+                while (it.hasNext()) {
+                    resumeStoppedApp |= handleEvent(it.nextEvent());
+                }
+
+                if (resumeStoppedApp) {
+                    eventSet.resume();
+                }
+            } catch (InterruptedException exc) {
+                // Do nothing. Any changes will be seen at top of loop.
+            } catch (VMDisconnectedException discExc) {
+                handleDisconnectedException();
+                break;
+            }
+        }
+        synchronized (this) {
+            completed = true;
+            notifyAll();
+        }
+    }
+
+    private boolean handleEvent(Event event) {
+        handleExitEvent(event);
+        return true;
+    }
+
+    private void handleExitEvent(Event event) {
+        if (event instanceof VMDeathEvent) {
+            reportVMExit.accept("VM Died");
+        } else if (event instanceof VMDisconnectEvent) {
+            connected = false;
+            reportVMExit.accept("VM Disconnected");
+        } else {
+            // ignore everything else
+        }
+    }
+
+    private synchronized void handleDisconnectedException() {
+        /*
+         * A VMDisconnectedException has happened while dealing with
+         * another event. We need to flush the event queue, dealing only
+         * with exit events (VMDeath, VMDisconnect) so that we terminate
+         * correctly.
+         */
+        EventQueue queue = vm.eventQueue();
+        while (connected) {
+            try {
+                EventSet eventSet = queue.remove();
+                EventIterator iter = eventSet.eventIterator();
+                while (iter.hasNext()) {
+                    handleExitEvent(iter.next());
+                }
+            } catch (InterruptedException exc) {
+                // ignore
+            } catch (InternalError exc) {
+                // ignore
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIExecutionControl.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2014, 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import com.sun.jdi.ReferenceType;
+import com.sun.jdi.VirtualMachine;
+import jdk.jshell.spi.ExecutionControl;
+import static java.util.stream.Collectors.toMap;
+
+/**
+ * Abstract JDI implementation of {@link jdk.jshell.spi.ExecutionControl}
+ */
+public abstract class JDIExecutionControl extends StreamingExecutionControl implements ExecutionControl {
+
+    /**
+     * Mapping from class names to JDI {@link ReferenceType}.
+     */
+    private final Map<String, ReferenceType> toReferenceType = new HashMap<>();
+
+    /**
+     * Create an instance.
+     * @param out the output from the remote agent
+     * @param in the input to the remote agent
+     */
+    protected JDIExecutionControl(ObjectOutput out, ObjectInput in) {
+        super(out, in);
+    }
+
+    /**
+     * Returns the JDI {@link VirtualMachine} instance.
+     *
+     * @return the virtual machine
+     * @throws EngineTerminationException if the VM is dead/disconnected
+     */
+    protected abstract VirtualMachine vm() throws EngineTerminationException;
+
+    /**
+     * Redefine the specified classes. Where 'redefine' is, as in JDI and JVMTI,
+     * an in-place replacement of the classes (preserving class identity) --
+     * that is, existing references to the class do not need to be recompiled.
+     * This implementation uses JDI
+     * {@link com.sun.jdi.VirtualMachine#redefineClasses(java.util.Map) }.
+     * It will be unsuccessful if
+     * the signature of the class has changed (see the JDI spec). The
+     * JShell-core is designed to adapt to unsuccessful redefine.
+     */
+    @Override
+    public void redefine(ClassBytecodes[] cbcs)
+            throws ClassInstallException, EngineTerminationException {
+        try {
+            // Convert to the JDI ReferenceType to class bytes map form needed
+            // by JDI.
+            VirtualMachine vm = vm();
+            Map<ReferenceType, byte[]> rmp = Stream.of(cbcs)
+                    .collect(toMap(
+                            cbc -> referenceType(vm, cbc.name()),
+                            cbc -> cbc.bytecodes()));
+            // Attempt redefine.  Throws exceptions on failure.
+            vm().redefineClasses(rmp);
+        } catch (EngineTerminationException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new ClassInstallException("redefine: " + ex.getMessage(), new boolean[cbcs.length]);
+        }
+    }
+
+    /**
+     * Returns the JDI {@link ReferenceType} corresponding to the specified
+     * class name.
+     *
+     * @param vm the current JDI {@link VirtualMachine} as returned by
+     * {@code vm()}
+     * @param name the class name to look-up
+     * @return the corresponding {@link ReferenceType}
+     */
+    protected ReferenceType referenceType(VirtualMachine vm, String name) {
+        return toReferenceType.computeIfAbsent(name, n -> nameToRef(vm, n));
+    }
+
+    private static ReferenceType nameToRef(VirtualMachine vm, String name) {
+        List<ReferenceType> rtl = vm.classesByName(name);
+        if (rtl.size() != 1) {
+            return null;
+        }
+        return rtl.get(0);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIInitiator.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import com.sun.jdi.Bootstrap;
+import com.sun.jdi.VirtualMachine;
+import com.sun.jdi.connect.Connector;
+import com.sun.jdi.connect.LaunchingConnector;
+import com.sun.jdi.connect.ListeningConnector;
+
+/**
+ * Sets up a JDI connection, providing the resulting JDI {@link VirtualMachine}
+ * and the {@link Process} the remote agent is running in.
+ */
+public class JDIInitiator {
+
+    private VirtualMachine vm;
+    private Process process = null;
+    private final Connector connector;
+    private final String remoteAgent;
+    private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
+
+    /**
+     * Start the remote agent and establish a JDI connection to it.
+     *
+     * @param port the socket port for (non-JDI) commands
+     * @param remoteVMOptions any user requested VM options
+     * @param remoteAgent full class name of remote agent to launch
+     * @param isLaunch does JDI do the launch? That is, LaunchingConnector,
+     * otherwise we start explicitly and use ListeningConnector
+     */
+    public JDIInitiator(int port, List<String> remoteVMOptions,
+            String remoteAgent, boolean isLaunch) {
+        this.remoteAgent = remoteAgent;
+        String connectorName
+                = isLaunch
+                        ? "com.sun.jdi.CommandLineLaunch"
+                        : "com.sun.jdi.SocketListen";
+        this.connector = findConnector(connectorName);
+        if (connector == null) {
+            throw new IllegalArgumentException("No connector named: " + connectorName);
+        }
+        Map<String, String> argumentName2Value
+                = isLaunch
+                        ? launchArgs(port, String.join(" ", remoteVMOptions))
+                        : new HashMap<>();
+        this.connectorArgs = mergeConnectorArgs(connector, argumentName2Value);
+        this.vm = isLaunch
+                ? launchTarget()
+                : listenTarget(port, remoteVMOptions);
+
+    }
+
+    /**
+     * Returns the resulting {@code VirtualMachine} instance.
+     *
+     * @return the virtual machine
+     */
+    public VirtualMachine vm() {
+        return vm;
+    }
+
+    /**
+     * Returns the launched process.
+     *
+     * @return the remote agent process
+     */
+    public Process process() {
+        return process;
+    }
+
+    /* launch child target vm */
+    private VirtualMachine launchTarget() {
+        LaunchingConnector launcher = (LaunchingConnector) connector;
+        try {
+            VirtualMachine new_vm = launcher.launch(connectorArgs);
+            process = new_vm.process();
+            return new_vm;
+        } catch (Exception ex) {
+            reportLaunchFail(ex, "launch");
+        }
+        return null;
+    }
+
+    /**
+     * Directly launch the remote agent and connect JDI to it with a
+     * ListeningConnector.
+     */
+    private VirtualMachine listenTarget(int port, List<String> remoteVMOptions) {
+        ListeningConnector listener = (ListeningConnector) connector;
+        try {
+            // Start listening, get the JDI connection address
+            String addr = listener.startListening(connectorArgs);
+            debug("Listening at address: " + addr);
+
+            // Launch the RemoteAgent requesting a connection on that address
+            String javaHome = System.getProperty("java.home");
+            List<String> args = new ArrayList<>();
+            args.add(javaHome == null
+                    ? "java"
+                    : javaHome + File.separator + "bin" + File.separator + "java");
+            args.add("-agentlib:jdwp=transport=" + connector.transport().name() +
+                    ",address=" + addr);
+            args.addAll(remoteVMOptions);
+            args.add(remoteAgent);
+            args.add("" + port);
+            ProcessBuilder pb = new ProcessBuilder(args);
+            process = pb.start();
+
+            // Forward out, err, and in
+            // Accept the connection from the remote agent
+            vm = listener.accept(connectorArgs);
+            listener.stopListening(connectorArgs);
+            return vm;
+        } catch (Exception ex) {
+            reportLaunchFail(ex, "listen");
+        }
+        return null;
+    }
+
+    private Connector findConnector(String name) {
+        for (Connector cntor
+                : Bootstrap.virtualMachineManager().allConnectors()) {
+            if (cntor.name().equals(name)) {
+                return cntor;
+            }
+        }
+        return null;
+    }
+
+    private Map<String, Connector.Argument> mergeConnectorArgs(Connector connector, Map<String, String> argumentName2Value) {
+        Map<String, Connector.Argument> arguments = connector.defaultArguments();
+
+        for (Entry<String, String> argumentEntry : argumentName2Value.entrySet()) {
+            String name = argumentEntry.getKey();
+            String value = argumentEntry.getValue();
+            Connector.Argument argument = arguments.get(name);
+
+            if (argument == null) {
+                throw new IllegalArgumentException("Argument is not defined for connector:" +
+                        name + " -- " + connector.name());
+            }
+
+            argument.setValue(value);
+        }
+
+        return arguments;
+    }
+
+    /**
+     * The JShell specific Connector args for the LaunchingConnector.
+     *
+     * @param portthe socket port for (non-JDI) commands
+     * @param remoteVMOptions any user requested VM options
+     * @return the argument map
+     */
+    private Map<String, String> launchArgs(int port, String remoteVMOptions) {
+        Map<String, String> argumentName2Value = new HashMap<>();
+        argumentName2Value.put("main", remoteAgent + " " + port);
+        argumentName2Value.put("options", remoteVMOptions);
+        return argumentName2Value;
+    }
+
+    private void reportLaunchFail(Exception ex, String context) {
+        throw new InternalError("Failed remote " + context + ": " + connector +
+                " -- " + connectorArgs, ex);
+    }
+
+    /**
+     * Log debugging information. Arguments as for {@code printf}.
+     *
+     * @param format a format string as described in Format string syntax
+     * @param args arguments referenced by the format specifiers in the format
+     * string.
+     */
+    private void debug(String format, Object... args) {
+        // Reserved for future logging
+    }
+
+    /**
+     * Log a serious unexpected internal exception.
+     *
+     * @param ex the exception
+     * @param where a description of the context of the exception
+     */
+    private void debug(Throwable ex, String where) {
+        // Reserved for future logging
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/LoaderDelegate.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.InternalException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
+
+/**
+ * This interface specifies the loading specific subset of
+ * {@link jdk.jshell.spi.ExecutionControl}.  For use in encapsulating the
+ * {@link java.lang.ClassLoader} implementation.
+ */
+public interface LoaderDelegate {
+
+    /**
+     * Attempts to load new classes.
+     *
+     * @param cbcs the class name and bytecodes to load
+     * @throws ClassInstallException exception occurred loading the classes,
+     * some or all were not loaded
+     * @throws NotImplementedException if not implemented
+     * @throws EngineTerminationException the execution engine has terminated
+     */
+    void load(ClassBytecodes[] cbcs)
+            throws ClassInstallException, NotImplementedException, EngineTerminationException;
+
+    /**
+     * Adds the path to the execution class path.
+     *
+     * @param path the path to add
+     * @throws EngineTerminationException the execution engine has terminated
+     * @throws InternalException an internal problem occurred
+     */
+    void addToClasspath(String path)
+            throws EngineTerminationException, InternalException;
+
+    /**
+     * Sets the execution class path to the specified path.
+     *
+     * @param path the path to add
+     * @throws EngineTerminationException the execution engine has terminated
+     * @throws InternalException an internal problem occurred
+     */
+    void setClasspath(String path)
+            throws EngineTerminationException, InternalException;
+
+    /**
+     * Finds the class with the specified binary name.
+     *
+     * @param name the binary name of the class
+     * @return the Class Object
+     * @throws ClassNotFoundException if the class could not be found
+     */
+    Class<?> findClass(String name) throws ClassNotFoundException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicReference;
+import jdk.jshell.spi.ExecutionControl;
+
+/**
+ * An implementation of {@link jdk.jshell.spi.ExecutionControl} which executes
+ * in the same JVM as the JShell-core.
+ *
+ * @author Grigory Ptashko
+ */
+public class LocalExecutionControl extends DirectExecutionControl {
+
+    private final Object STOP_LOCK = new Object();
+    private boolean userCodeRunning = false;
+    private ThreadGroup execThreadGroup;
+
+    /**
+     * Creates a local ExecutionControl instance.
+     *
+     * @return the generator
+     */
+    public static ExecutionControl.Generator create() {
+        return env -> new LocalExecutionControl();
+    }
+
+    /**
+     * Creates an instance, delegating loader operations to the specified
+     * delegate.
+     *
+     * @param loaderDelegate the delegate to handle loading classes
+     */
+    public LocalExecutionControl(LoaderDelegate loaderDelegate) {
+        super(loaderDelegate);
+    }
+
+    /**
+     * Create an instance using the default class loading.
+     */
+    public LocalExecutionControl() {
+    }
+
+    @Override
+    protected String invoke(Method doitMethod) throws Exception {
+        execThreadGroup = new ThreadGroup("JShell process local execution");
+
+        AtomicReference<InvocationTargetException> iteEx = new AtomicReference<>();
+        AtomicReference<IllegalAccessException> iaeEx = new AtomicReference<>();
+        AtomicReference<NoSuchMethodException> nmeEx = new AtomicReference<>();
+        AtomicReference<Boolean> stopped = new AtomicReference<>(false);
+
+        Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
+            if (e instanceof InvocationTargetException) {
+                if (e.getCause() instanceof ThreadDeath) {
+                    stopped.set(true);
+                } else {
+                    iteEx.set((InvocationTargetException) e);
+                }
+            } else if (e instanceof IllegalAccessException) {
+                iaeEx.set((IllegalAccessException) e);
+            } else if (e instanceof NoSuchMethodException) {
+                nmeEx.set((NoSuchMethodException) e);
+            } else if (e instanceof ThreadDeath) {
+                stopped.set(true);
+            }
+        });
+
+        final Object[] res = new Object[1];
+        Thread snippetThread = new Thread(execThreadGroup, () -> {
+            try {
+                res[0] = doitMethod.invoke(null, new Object[0]);
+            } catch (InvocationTargetException e) {
+                if (e.getCause() instanceof ThreadDeath) {
+                    stopped.set(true);
+                } else {
+                    iteEx.set(e);
+                }
+            } catch (IllegalAccessException e) {
+                iaeEx.set(e);
+            } catch (ThreadDeath e) {
+                stopped.set(true);
+            }
+        });
+
+        snippetThread.start();
+        Thread[] threadList = new Thread[execThreadGroup.activeCount()];
+        execThreadGroup.enumerate(threadList);
+        for (Thread thread : threadList) {
+            if (thread != null) {
+                thread.join();
+            }
+        }
+
+        if (stopped.get()) {
+            throw new StoppedException();
+        }
+
+        if (iteEx.get() != null) {
+            throw iteEx.get();
+        } else if (nmeEx.get() != null) {
+            throw nmeEx.get();
+        } else if (iaeEx.get() != null) {
+            throw iaeEx.get();
+        }
+
+        return valueString(res[0]);
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public void stop() throws EngineTerminationException, InternalException {
+        synchronized (STOP_LOCK) {
+            if (!userCodeRunning) {
+                return;
+            }
+            if (execThreadGroup == null) {
+                throw new InternalException("Process-local code snippets thread group is null. Aborting stop.");
+            }
+
+            execThreadGroup.stop();
+        }
+    }
+
+    @Override
+    protected void clientCodeEnter() {
+        synchronized (STOP_LOCK) {
+            userCodeRunning = true;
+        }
+    }
+
+    @Override
+    protected void clientCodeLeave() {
+        synchronized (STOP_LOCK) {
+            userCodeRunning = false;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/MultiplexingOutputStream.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Packetize an OutputStream, dividing it into named channels.
+ *
+ * @author Jan Lahoda
+ */
+class MultiplexingOutputStream extends OutputStream {
+
+    private static final int PACKET_SIZE = 127;
+    private final byte[] name;
+    private final OutputStream delegate;
+
+    MultiplexingOutputStream(String name, OutputStream delegate) {
+        try {
+            this.name = name.getBytes("UTF-8");
+            this.delegate = delegate;
+        } catch (UnsupportedEncodingException ex) {
+            throw new IllegalStateException(ex); //should not happen
+        }
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        synchronized (delegate) {
+            delegate.write(name.length); //assuming the len is small enough to fit into byte
+            delegate.write(name);
+            delegate.write(1);
+            delegate.write(b);
+            delegate.flush();
+        }
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        synchronized (delegate) {
+            int i = 0;
+            while (len > 0) {
+                int size = Math.min(PACKET_SIZE, len);
+                delegate.write(name.length); //assuming the len is small enough to fit into byte
+                delegate.write(name);
+                delegate.write(size);
+                delegate.write(b, off + i, size);
+                i += size;
+                len -= size;
+            }
+            delegate.flush();
+        }
+    }
+
+    @Override
+    public void flush() throws IOException {
+        super.flush();
+        delegate.flush();
+    }
+
+    @Override
+    public void close() throws IOException {
+        super.close();
+        delegate.close();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/PipeInputStream.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.io.InputStream;
+
+/**
+ *
+ * @author Jan Lahoda
+ */
+class PipeInputStream extends InputStream {
+
+    private static final int INITIAL_SIZE = 128;
+    private int[] buffer = new int[INITIAL_SIZE];
+    private int start;
+    private int end;
+    private boolean closed;
+
+    @Override
+    public synchronized int read() {
+        while (start == end) {
+            if (closed) {
+                return -1;
+            }
+            try {
+                wait();
+            } catch (InterruptedException ex) {
+                //ignore
+            }
+        }
+        try {
+            return buffer[start];
+        } finally {
+            start = (start + 1) % buffer.length;
+        }
+    }
+
+    public synchronized void write(int b) {
+        if (closed) {
+            throw new IllegalStateException("Already closed.");
+        }
+        int newEnd = (end + 1) % buffer.length;
+        if (newEnd == start) {
+            //overflow:
+            int[] newBuffer = new int[buffer.length * 2];
+            int rightPart = (end > start ? end : buffer.length) - start;
+            int leftPart = end > start ? 0 : start - 1;
+            System.arraycopy(buffer, start, newBuffer, 0, rightPart);
+            System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart);
+            buffer = newBuffer;
+            start = 0;
+            end = rightPart + leftPart;
+            newEnd = end + 1;
+        }
+        buffer[end] = b;
+        end = newEnd;
+        notifyAll();
+    }
+
+    @Override
+    public synchronized void close() {
+        closed = true;
+        notifyAll();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteCodes.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2014, 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+/**
+ * Communication constants shared between the main process and the remote
+ * execution process.  These are not enums to allow for future expansion, and
+ * remote/local of different versions.
+ *
+ * @author Robert Field
+ */
+class RemoteCodes {
+
+    /**
+     * Command prefix markers.
+     */
+    static final int COMMAND_PREFIX = 0xC03DC03D;
+
+    // Command codes
+
+    /**
+     * Exit the the agent.
+     */
+    static final String CMD_CLOSE          = "CMD_CLOSE";
+    /**
+     * Load classes.
+     */
+    static final String CMD_LOAD           = "CMD_LOAD";
+    /**
+     * Redefine classes.
+     */
+    static final String CMD_REDEFINE       = "CMD_REDEFINE";
+    /**
+     * Invoke a method.
+     */
+    static final String CMD_INVOKE         = "CMD_INVOKE";
+    /**
+     * Retrieve the value of a variable.
+     */
+    static final String CMD_VAR_VALUE      = "CMD_VAR_VALUE";
+    /**
+     * Add to the class-path.
+     */
+    static final String CMD_ADD_CLASSPATH  = "CMD_ADD_CLASSPATH";
+    /**
+     * Set the class-path.
+     */
+    static final String CMD_SET_CLASSPATH  = "CMD_SET_CLASSPATH";
+    /**
+     * Stop an invoke.
+     */
+    static final String CMD_STOP           = "CMD_STOP";
+
+    // Return result codes
+
+    /**
+     * The command succeeded.
+     */
+    static final int RESULT_SUCCESS                 = 100;
+    /**
+     * Unbidden execution engine termination.
+     */
+    static final int RESULT_TERMINATED              = 101;
+    /**
+     * Command not implemented.
+     */
+    static final int RESULT_NOT_IMPLEMENTED         = 102;
+    /**
+     * The command failed.
+     */
+    static final int RESULT_INTERNAL_PROBLEM        = 103;
+    /**
+     * User exception encountered.
+     */
+    static final int RESULT_USER_EXCEPTION          = 104;
+    /**
+     * Corralled code exception encountered.
+     */
+    static final int RESULT_CORRALLED               = 105;
+    /**
+     * Exception encountered during class load/redefine.
+     */
+    static final int RESULT_CLASS_INSTALL_EXCEPTION = 106;
+    /**
+     * The invoke has been stopped.
+     */
+    static final int RESULT_STOPPED                 = 107;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2014, 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.net.Socket;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import jdk.jshell.spi.ExecutionControl;
+import static jdk.jshell.execution.Util.forwardExecutionControlAndIO;
+
+/**
+ * The remote agent runs in the execution process (separate from the main JShell
+ * process). This agent loads code over a socket from the main JShell process,
+ * executes the code, and other misc, Specialization of
+ * {@link DirectExecutionControl} which adds stop support controlled by
+ * an external process. Designed to work with {@link JDIDefaultExecutionControl}.
+ *
+ * @author Jan Lahoda
+ * @author Robert Field
+ */
+public class RemoteExecutionControl extends DirectExecutionControl implements ExecutionControl {
+
+    /**
+     * Launch the agent, connecting to the JShell-core over the socket specified
+     * in the command-line argument.
+     *
+     * @param args standard command-line arguments, expectation is the socket
+     * number is the only argument
+     * @throws Exception any unexpected exception
+     */
+    public static void main(String[] args) throws Exception {
+        String loopBack = null;
+        Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
+        InputStream inStream = socket.getInputStream();
+        OutputStream outStream = socket.getOutputStream();
+        Map<String, Consumer<OutputStream>> chans = new HashMap<>();
+        chans.put("out", st -> System.setOut(new PrintStream(st, true)));
+        chans.put("err", st -> System.setErr(new PrintStream(st, true)));
+        forwardExecutionControlAndIO(new RemoteExecutionControl(), inStream, outStream, chans);
+    }
+
+    // These three variables are used by the main JShell process in interrupting
+    // the running process.  Access is via JDI, so the reference is not visible
+    // to code inspection.
+    private boolean inClientCode; // Queried by the main process (in superclass)
+    private boolean expectingStop; // Set by the main process
+// Set by the main process
+
+    // thrown by the main process via JDI:
+    private final StopExecutionException stopException = new StopExecutionException();
+
+    /**
+     * Creates an instance, delegating loader operations to the specified
+     * delegate.
+     *
+     * @param loaderDelegate the delegate to handle loading classes
+     */
+    public RemoteExecutionControl(LoaderDelegate loaderDelegate) {
+        super(loaderDelegate);
+    }
+
+    /**
+     * Create an instance using the default class loading.
+     */
+    public RemoteExecutionControl() {
+    }
+
+    @Override
+    public void stop() throws EngineTerminationException, InternalException {
+        // handled by JDI
+    }
+
+    // Overridden only so this stack frame is seen
+    @Override
+    protected String invoke(Method doitMethod) throws Exception {
+        return super.invoke(doitMethod);
+    }
+
+    // Overridden only so this stack frame is seen
+    @Override
+    public String varValue(String className, String varName) throws RunException, EngineTerminationException, InternalException {
+        return super.varValue(className, varName);
+    }
+
+    @Override
+    protected String throwConvertedInvocationException(Throwable cause) throws RunException, InternalException {
+        if (cause instanceof StopExecutionException) {
+            expectingStop = false;
+            throw new StoppedException();
+        } else {
+            return super.throwConvertedInvocationException(cause);
+        }
+    }
+
+    @Override
+    protected String throwConvertedOtherException(Throwable ex) throws RunException, InternalException {
+        if (ex instanceof StopExecutionException ||
+                 ex.getCause() instanceof StopExecutionException) {
+            expectingStop = false;
+            throw new StoppedException();
+        }
+        return super.throwConvertedOtherException(ex);
+    }
+
+    @Override
+    protected void clientCodeEnter() {
+        expectingStop = false;
+        inClientCode = true;
+    }
+
+    @Override
+    protected void clientCodeLeave() throws InternalException {
+        inClientCode = false;
+        while (expectingStop) {
+            try {
+                Thread.sleep(0);
+            } catch (InterruptedException ex) {
+                throw new InternalException("*** Sleep interrupted while waiting for stop exception: " + ex);
+            }
+        }
+    }
+
+    @SuppressWarnings("serial")             // serialVersionUID intentionally omitted
+    private class StopExecutionException extends ThreadDeath {
+
+        @Override
+        public synchronized Throwable fillInStackTrace() {
+            return this;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import jdk.jshell.JShellException;
+import jdk.jshell.spi.ExecutionControl;
+import static jdk.jshell.execution.RemoteCodes.*;
+
+/**
+ * An implementation of the {@link jdk.jshell.spi.ExecutionControl}
+ * execution engine SPI which streams requests to a remote agent where
+ * execution takes place.
+ *
+ * @author Robert Field
+ */
+public class StreamingExecutionControl implements ExecutionControl {
+
+    private final ObjectOutput out;
+    private final ObjectInput in;
+
+    /**
+     * Creates an instance.
+     *
+     * @param out the output for commands
+     * @param in the input for command responses
+     */
+    public StreamingExecutionControl(ObjectOutput out, ObjectInput in) {
+        this.out = out;
+        this.in = in;
+    }
+
+    @Override
+    public void load(ClassBytecodes[] cbcs)
+            throws ClassInstallException, NotImplementedException, EngineTerminationException {
+        try {
+            // Send a load command to the remote agent.
+            writeCommand(CMD_LOAD);
+            out.writeObject(cbcs);
+            out.flush();
+            // Retrieve and report results from the remote agent.
+            readAndReportClassInstallResult();
+        } catch (IOException ex) {
+            throw new EngineTerminationException("Exception writing remote load: " + ex);
+        }
+    }
+
+    @Override
+    public void redefine(ClassBytecodes[] cbcs)
+            throws ClassInstallException, NotImplementedException, EngineTerminationException {
+        try {
+            // Send a load command to the remote agent.
+            writeCommand(CMD_REDEFINE);
+            out.writeObject(cbcs);
+            out.flush();
+            // Retrieve and report results from the remote agent.
+            readAndReportClassInstallResult();
+        } catch (IOException ex) {
+            throw new EngineTerminationException("Exception writing remote redefine: " + ex);
+        }
+    }
+
+    @Override
+    public String invoke(String classname, String methodname)
+            throws RunException, EngineTerminationException, InternalException {
+        try {
+            // Send the invoke command to the remote agent.
+            writeCommand(CMD_INVOKE);
+            out.writeUTF(classname);
+            out.writeUTF(methodname);
+            out.flush();
+            // Retrieve and report results from the remote agent.
+            readAndReportExecutionResult();
+            String result = in.readUTF();
+            return result;
+        } catch (IOException ex) {
+            throw new EngineTerminationException("Exception writing remote invoke: " + ex);
+        }
+    }
+
+    @Override
+    public String varValue(String classname, String varname)
+            throws RunException, EngineTerminationException, InternalException {
+        try {
+            // Send the variable-value command to the remote agent.
+            writeCommand(CMD_VAR_VALUE);
+            out.writeUTF(classname);
+            out.writeUTF(varname);
+            out.flush();
+            // Retrieve and report results from the remote agent.
+            readAndReportExecutionResult();
+            String result = in.readUTF();
+            return result;
+        } catch (IOException ex) {
+            throw new EngineTerminationException("Exception writing remote varValue: " + ex);
+        }
+    }
+
+
+    @Override
+    public void addToClasspath(String path)
+            throws EngineTerminationException, InternalException {
+        try {
+            // Send the classpath addition command to the remote agent.
+            writeCommand(CMD_ADD_CLASSPATH);
+            out.writeUTF(path);
+            out.flush();
+            // Retrieve and report results from the remote agent.
+            readAndReportClassSimpleResult();
+        } catch (IOException ex) {
+            throw new EngineTerminationException("Exception writing remote add to classpath: " + ex);
+        }
+    }
+
+    @Override
+    public void setClasspath(String path)
+            throws EngineTerminationException, InternalException {
+        try {
+            // Send the classpath addition command to the remote agent.
+            writeCommand(CMD_SET_CLASSPATH);
+            out.writeUTF(path);
+            out.flush();
+            // Retrieve and report results from the remote agent.
+            readAndReportClassSimpleResult();
+        } catch (IOException ex) {
+            throw new EngineTerminationException("Exception writing remote set classpath: " + ex);
+        }
+    }
+
+    @Override
+    public void stop()
+            throws EngineTerminationException, InternalException {
+        try {
+            // Send the variable-value command to the remote agent.
+            writeCommand(CMD_STOP);
+            out.flush();
+        } catch (IOException ex) {
+            throw new EngineTerminationException("Exception writing remote stop: " + ex);
+        }
+    }
+
+    @Override
+    public Object extensionCommand(String command, Object arg)
+            throws RunException, EngineTerminationException, InternalException {
+        try {
+            writeCommand(command);
+            out.writeObject(arg);
+            out.flush();
+            // Retrieve and report results from the remote agent.
+            readAndReportExecutionResult();
+            Object result = in.readObject();
+            return result;
+        } catch (IOException | ClassNotFoundException ex) {
+            throw new EngineTerminationException("Exception transmitting remote extensionCommand: "
+                    + command + " -- " + ex);
+        }
+    }
+
+    /**
+     * Closes the execution engine. Send an exit command to the remote agent.
+     */
+    @Override
+    public void close() {
+        try {
+            writeCommand(CMD_CLOSE);
+            out.flush();
+        } catch (IOException ex) {
+            // ignore;
+        }
+    }
+
+    private void writeCommand(String cmd) throws IOException {
+        out.writeInt(COMMAND_PREFIX);
+        out.writeUTF(cmd);
+    }
+
+    /**
+     * Reports results from a remote agent command that does not expect
+     * exceptions.
+     */
+    private void readAndReportClassSimpleResult() throws EngineTerminationException, InternalException {
+        try {
+            int status = in.readInt();
+            switch (status) {
+                case RESULT_SUCCESS:
+                    return;
+                case RESULT_NOT_IMPLEMENTED: {
+                    String message = in.readUTF();
+                    throw new NotImplementedException(message);
+                }
+                case RESULT_INTERNAL_PROBLEM: {
+                    String message = in.readUTF();
+                    throw new InternalException(message);
+                }
+                case RESULT_TERMINATED: {
+                    String message = in.readUTF();
+                    throw new EngineTerminationException(message);
+                }
+                default: {
+                    throw new EngineTerminationException("Bad remote result code: " + status);
+                }
+            }
+        } catch (IOException ex) {
+            throw new EngineTerminationException(ex.toString());
+        }
+    }
+
+    /**
+     * Reports results from a remote agent command that does not expect
+     * exceptions.
+     */
+    private void readAndReportClassInstallResult() throws ClassInstallException,
+            NotImplementedException, EngineTerminationException {
+        try {
+            int status = in.readInt();
+            switch (status) {
+                case RESULT_SUCCESS:
+                    return;
+                case RESULT_NOT_IMPLEMENTED: {
+                    String message = in.readUTF();
+                    throw new NotImplementedException(message);
+                }
+                case RESULT_CLASS_INSTALL_EXCEPTION: {
+                    String message = in.readUTF();
+                    boolean[] loaded = (boolean[]) in.readObject();
+                    throw new ClassInstallException(message, loaded);
+                }
+                case RESULT_TERMINATED: {
+                    String message = in.readUTF();
+                    throw new EngineTerminationException(message);
+                }
+                default: {
+                    throw new EngineTerminationException("Bad remote result code: " + status);
+                }
+            }
+        } catch (IOException | ClassNotFoundException ex) {
+            throw new EngineTerminationException(ex.toString());
+        }
+    }
+
+    /**
+     * Reports results from a remote agent command that expects runtime
+     * exceptions.
+     *
+     * @return true if successful
+     * @throws IOException if the connection has dropped
+     * @throws JShellException {@link jdk.jshell.EvalException}, if a user
+     * exception was encountered on invoke;
+     * {@link jdk.jshell.UnresolvedReferenceException}, if an unresolved
+     * reference was encountered
+     * @throws java.lang.ClassNotFoundException
+     */
+    private void readAndReportExecutionResult() throws RunException,
+            EngineTerminationException, InternalException {
+        try {
+            int status = in.readInt();
+            switch (status) {
+                case RESULT_SUCCESS:
+                    return;
+                case RESULT_NOT_IMPLEMENTED: {
+                    String message = in.readUTF();
+                    throw new NotImplementedException(message);
+                }
+                case RESULT_USER_EXCEPTION: {
+                    // A user exception was encountered.
+                    String message = in.readUTF();
+                    String exceptionClassName = in.readUTF();
+                    StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
+                    throw new UserException(message, exceptionClassName, elems);
+                }
+                case RESULT_CORRALLED: {
+                    // An unresolved reference was encountered.
+                    int id = in.readInt();
+                    StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
+                    ResolutionException re = new ResolutionException(id, elems);
+                    throw re;
+                }
+                case RESULT_STOPPED: {
+                    // Execution was aborted by the stop()
+                    throw new StoppedException();
+                }
+                case RESULT_INTERNAL_PROBLEM: {
+                    // An internal error has occurred.
+                    String message = in.readUTF();
+                    throw new InternalException(message);
+                }
+                case RESULT_TERMINATED: {
+                    String message = in.readUTF();
+                    throw new EngineTerminationException(message);
+                }
+                default: {
+                    throw new EngineTerminationException("Bad remote result code: " + status);
+                }
+            }
+        } catch (IOException | ClassNotFoundException ex) {
+            throw new EngineTerminationException(ex.toString());
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2016, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 jdk.jshell.execution;
+
+import jdk.jshell.spi.ExecutionEnv;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Consumer;
+import com.sun.jdi.VirtualMachine;
+import jdk.jshell.spi.ExecutionControl;
+
+
+/**
+ * Miscellaneous utility methods for setting-up implementations of
+ * {@link ExecutionControl}. Particularly implementations with remote
+ * execution.
+ *
+ * @author Jan Lahoda
+ * @author Robert Field
+ */
+public class Util {
+
+    // never instanciated
+    private Util() {}
+
+    /**
+     * Create a composite {@link ExecutionControl.Generator} instance that, when
+     * generating, will try each specified generator until successfully creating
+     * an {@link ExecutionControl} instance, or, if all fail, will re-throw the
+     * first exception.
+     *
+     * @param gec0 the first instance to try
+     * @param gecs the second through Nth instance to try
+     * @return the fail-over generator
+     */
+    public static ExecutionControl.Generator failOverExecutionControlGenerator(
+            ExecutionControl.Generator gec0, ExecutionControl.Generator... gecs) {
+        return (ExecutionEnv env) -> {
+            Throwable thrown;
+            try {
+                return gec0.generate(env);
+            } catch (Throwable ex) {
+                thrown = ex;
+            }
+            for (ExecutionControl.Generator gec : gecs) {
+                try {
+                    return gec.generate(env);
+                } catch (Throwable ignore) {
+                    // only care about the first, and only if they all fail
+                }
+            }
+            throw thrown;
+        };
+    }
+
+    /**
+     * Forward commands from the input to the specified {@link ExecutionControl}
+     * instance, then responses back on the output.
+     * @param ec the direct instance of {@link ExecutionControl} to process commands
+     * @param in the command input
+     * @param out the command response output
+     */
+    public static void forwardExecutionControl(ExecutionControl ec,
+            ObjectInput in, ObjectOutput out) {
+        new ExecutionControlForwarder(ec, in, out).commandLoop();
+    }
+
+    /**
+     * Forward commands from the input to the specified {@link ExecutionControl}
+     * instance, then responses back on the output.
+     * @param ec the direct instance of {@link ExecutionControl} to process commands
+     * @param inStream the stream from which to create the command input
+     * @param outStream the stream that will carry {@code System.out},
+     * {@code System.err}, any specified auxiliary channels, and the
+     * command response output.
+     * @param streamMap a map between names of additional streams to carry and setters
+     * for the stream
+     * @throws IOException if there are errors using the passed streams
+     */
+    public static void forwardExecutionControlAndIO(ExecutionControl ec,
+            InputStream inStream, OutputStream outStream,
+            Map<String, Consumer<OutputStream>> streamMap) throws IOException {
+        ObjectInputStream cmdIn = new ObjectInputStream(inStream);
+        for (Entry<String, Consumer<OutputStream>> e : streamMap.entrySet()) {
+            e.getValue().accept(multiplexingOutputStream(e.getKey(), outStream));
+        }
+        ObjectOutputStream cmdOut = new ObjectOutputStream(multiplexingOutputStream("command", outStream));
+        forwardExecutionControl(ec, cmdIn, cmdOut);
+    }
+
+    static OutputStream multiplexingOutputStream(String label, OutputStream outputStream) {
+        return new MultiplexingOutputStream(label, outputStream);
+    }
+
+    /**
+     * Reads from an InputStream which has been packetized and write its contents
+     * to the out and err OutputStreams; Copies the command stream.
+     * @param input the packetized input stream
+     * @param streamMap a map between stream names and the output streams to forward
+     * @return the command stream
+     * @throws IOException if setting up the streams raised an exception
+     */
+    public static ObjectInput remoteInput(InputStream input,
+            Map<String, OutputStream> streamMap) throws IOException {
+        PipeInputStream commandIn = new PipeInputStream();
+        new DemultiplexInput(input, commandIn, streamMap).start();
+        return new ObjectInputStream(commandIn);
+    }
+
+    /**
+     * Monitor the JDI event stream for {@link com.sun.jdi.event.VMDeathEvent}
+     * and {@link com.sun.jdi.event.VMDisconnectEvent}. If encountered, invokes
+     * {@code unbiddenExitHandler}.
+     *
+     * @param vm the virtual machine to check
+     * @param unbiddenExitHandler the handler, which will accept the exit
+     * information
+     */
+    public static void detectJDIExitEvent(VirtualMachine vm, Consumer<String> unbiddenExitHandler) {
+        if (vm.canBeModified()) {
+            new JDIEventHandler(vm, unbiddenExitHandler).start();
+        }
+    }
+
+    /**
+     * Creates a Thread that will ship all input to the remote agent.
+     *
+     * @param inputStream the user input
+     * @param outStream the input to the remote agent
+     * @param handler a failure handler
+     */
+    public static void forwardInputToRemote(final InputStream inputStream,
+            final OutputStream outStream, final Consumer<Exception> handler) {
+        Thread thr = new Thread("input reader") {
+            @Override
+            public void run() {
+                try {
+                    byte[] buf = new byte[256];
+                    int cnt;
+                    while ((cnt = inputStream.read(buf)) != -1) {
+                        outStream.write(buf, 0, cnt);
+                        outStream.flush();
+                    }
+                } catch (Exception ex) {
+                    handler.accept(ex);
+                }
+            }
+        };
+        thr.setPriority(Thread.MAX_PRIORITY - 1);
+        thr.start();
+    }
+
+}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java	Wed Jul 20 23:19:09 2016 -0700
@@ -22,62 +22,152 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-
 package jdk.jshell.spi;
 
-import java.util.Collection;
-import jdk.jshell.JShellException;
+import java.io.Serializable;
 
 /**
- * This interface specifies the functionality that must provided to implement
- * a pluggable JShell execution engine.
+ * This interface specifies the functionality that must provided to implement a
+ * pluggable JShell execution engine.
  * <p>
- * The audience for this Service Provider Interface is engineers
- * wishing to implement their own version of the execution engine in support
- * of the JShell API.  This is NOT a part of the JShell API.
+ * The audience for this Service Provider Interface is engineers wishing to
+ * implement their own version of the execution engine in support of the JShell
+ * API.
  * <p>
- * A Snippet is compiled into code wrapped in a 'wrapper class'.  The execution
- * engine is used by the core JShell implementation to load and, for
- * executable Snippets, execute the Snippet.
+ * A Snippet is compiled into code wrapped in a 'wrapper class'. The execution
+ * engine is used by the core JShell implementation to load and, for executable
+ * Snippets, execute the Snippet.
  * <p>
  * Methods defined in this interface should only be called by the core JShell
  * implementation.
  * <p>
- * To install an instance of ExecutionControl, it is passed to
- * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }.
+ * To install an {@code ExecutionControl}, its {@code Generator} is passed to
+ * {@link jdk.jshell.JShell.Builder#executionEngine(ExecutionControl.Generator)  }.
  */
 public interface ExecutionControl {
 
     /**
-     * Represents the current status of a class in the execution engine.
+     * Defines a functional interface for creating {@link ExecutionControl}
+     * instances.
      */
-    public enum ClassStatus {
-        /**
-         * Class is not known to the execution engine (not loaded).
-         */
-        UNKNOWN,
-
-        /**
-         * Class is loaded, but the loaded/redefined bytes do not match those
-         * returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }.
-         */
-        NOT_CURRENT,
+    public interface Generator {
 
         /**
-         * Class is loaded and loaded/redefined bytes match those
-         * returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }.
+         * Generates an execution engine, given an execution environment.
+         *
+         * @param env the context in which the {@link ExecutionControl} is to
+         * be created
+         * @return the created instance
+         * @throws Throwable if problems occurred
          */
-        CURRENT
-    };
+        ExecutionControl generate(ExecutionEnv env) throws Throwable;
+    }
+
+    /**
+     * Attempts to load new classes.
+     *
+     * @param cbcs the class name and bytecodes to load
+     * @throws ClassInstallException exception occurred loading the classes,
+     * some or all were not loaded
+     * @throws NotImplementedException if not implemented
+     * @throws EngineTerminationException the execution engine has terminated
+     */
+    void load(ClassBytecodes[] cbcs)
+            throws ClassInstallException, NotImplementedException, EngineTerminationException;
+
+    /**
+     * Attempts to redefine previously loaded classes.
+     *
+     * @param cbcs the class name and bytecodes to redefine
+     * @throws ClassInstallException exception occurred redefining the classes,
+     * some or all were not redefined
+     * @throws NotImplementedException if not implemented
+     * @throws EngineTerminationException the execution engine has terminated
+     */
+    void redefine(ClassBytecodes[] cbcs)
+            throws ClassInstallException, NotImplementedException, EngineTerminationException;
+
+    /**
+     * Invokes an executable Snippet by calling a method on the specified
+     * wrapper class. The method must have no arguments and return String.
+     *
+     * @param className the class whose method should be invoked
+     * @param methodName the name of method to invoke
+     * @return the result of the execution or null if no result
+     * @throws UserException the invoke raised a user exception
+     * @throws ResolutionException the invoke attempted to directly or
+     * indirectly invoke an unresolved snippet
+     * @throws StoppedException if the {@code invoke()} was canceled by
+     * {@link ExecutionControl#stop}
+     * @throws EngineTerminationException the execution engine has terminated
+     * @throws InternalException an internal problem occurred
+     */
+    String invoke(String className, String methodName)
+            throws RunException, EngineTerminationException, InternalException;
 
     /**
-     * Initializes the instance. No methods in this interface can be called
-     * before this.
+     * Returns the value of a variable.
+     *
+     * @param className the name of the wrapper class of the variable
+     * @param varName the name of the variable
+     * @return the value of the variable
+     * @throws UserException formatting the value raised a user exception
+     * @throws ResolutionException formatting the value attempted to directly or
+     * indirectly invoke an unresolved snippet
+     * @throws StoppedException if the formatting the value was canceled by
+     * {@link ExecutionControl#stop}
+     * @throws EngineTerminationException the execution engine has terminated
+     * @throws InternalException an internal problem occurred
+     */
+    String varValue(String className, String varName)
+            throws RunException, EngineTerminationException, InternalException;
+
+    /**
+     * Adds the path to the execution class path.
+     *
+     * @param path the path to add
+     * @throws EngineTerminationException the execution engine has terminated
+     * @throws InternalException an internal problem occurred
+     */
+    void addToClasspath(String path)
+            throws EngineTerminationException, InternalException;
+
+    /**
+     * Sets the execution class path to the specified path.
      *
-     * @param env the execution environment information provided by JShell
-     * @throws Exception if the instance is unable to initialize
+     * @param path the path to add
+     * @throws EngineTerminationException the execution engine has terminated
+     * @throws InternalException an internal problem occurred
+     */
+    void setClasspath(String path)
+            throws EngineTerminationException, InternalException;
+
+    /**
+     * Interrupts a running invoke.
+     *
+     * @throws EngineTerminationException the execution engine has terminated
+     * @throws InternalException an internal problem occurred
      */
-    void start(ExecutionEnv env) throws Exception;
+    void stop()
+            throws EngineTerminationException, InternalException;
+
+    /**
+     * Run a non-standard command (or a standard command from a newer version).
+     *
+     * @param command the non-standard command
+     * @param arg the commands argument
+     * @return the commands return value
+     * @throws UserException the command raised a user exception
+     * @throws ResolutionException the command attempted to directly or
+     * indirectly invoke an unresolved snippet
+     * @throws StoppedException if the command was canceled by
+     * {@link ExecutionControl#stop}
+     * @throws EngineTerminationException the execution engine has terminated
+     * @throws NotImplementedException if not implemented
+     * @throws InternalException an internal problem occurred
+     */
+    Object extensionCommand(String command, Object arg)
+            throws RunException, EngineTerminationException, InternalException;
 
     /**
      * Shuts down this execution engine. Implementation should free all
@@ -88,67 +178,206 @@
     void close();
 
     /**
-     * Adds the path to the execution class path.
-     *
-     * @param path the path to add
-     * @return true if successful
+     * Bundles class name with class bytecodes.
      */
-    boolean addToClasspath(String path);
+    public static final class ClassBytecodes implements Serializable {
+
+        private static final long serialVersionUID = 0xC1A55B47EC0DE5L;
+        private final String name;
+        private final byte[] bytecodes;
+
+        /**
+         * Creates a name/bytecode pair.
+         * @param name the class name
+         * @param bytecodes the class bytecodes
+         */
+        public ClassBytecodes(String name, byte[] bytecodes) {
+            this.name = name;
+            this.bytecodes = bytecodes;
+        }
+
+        /**
+         * The bytecodes for the class.
+         *
+         * @return the bytecodes
+         */
+        public byte[] bytecodes() {
+            return bytecodes;
+        }
+
+        /**
+         * The class name.
+         *
+         * @return the class name
+         */
+        public String name() {
+            return name;
+        }
+    }
 
     /**
-     * Invokes an executable Snippet by calling a method on the specified
-     * wrapper class. The method must have no arguments and return String.
-     *
-     * @param classname the class whose method should be invoked
-     * @param methodname the name of method to invoke
-     * @return the result of the execution or null if no result
-     * @throws JShellException if a user exception if thrown,
-     * {@link jdk.jshell.EvalException EvalException} will be thrown; if an
-     * unresolved reference is encountered,
-     * {@link jdk.jshell.UnresolvedReferenceException UnresolvedReferenceException}
-     * will be thrown
+     * The abstract base of all {@code ExecutionControl} exceptions.
      */
-    String invoke(String classname, String methodname) throws JShellException;
+    public static abstract class ExecutionControlException extends Exception {
+
+        private static final long serialVersionUID = 1L;
+
+        public ExecutionControlException(String message) {
+            super(message);
+        }
+    }
 
     /**
-     * Attempts to load new classes. Class bytes are retrieved from
-     * {@link ExecutionEnv#getClassBytes(java.lang.String) }
-     *
-     * @param classes list of class names to load
-     * @return true if load succeeded
+     * Unbidden execution engine termination has occurred.
+     */
+    public static class EngineTerminationException extends ExecutionControlException {
+
+        private static final long serialVersionUID = 1L;
+
+        public EngineTerminationException(String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * The command is not implemented.
      */
-    boolean load(Collection<String> classes);
+    public static class NotImplementedException extends InternalException {
+
+        private static final long serialVersionUID = 1L;
+
+        public NotImplementedException(String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * An internal problem has occurred.
+     */
+    public static class InternalException extends ExecutionControlException {
+
+        private static final long serialVersionUID = 1L;
+
+        public InternalException(String message) {
+            super(message);
+        }
+    }
 
     /**
-     * Attempts to redefine previously loaded classes. Class bytes are retrieved
-     * from {@link ExecutionEnv#getClassBytes(java.lang.String) }
-     *
-     * @param classes list of class names to redefine
-     * @return true if redefine succeeded
+     * A class install (load or redefine) encountered a problem.
      */
-    boolean redefine(Collection<String> classes);
+    public static class ClassInstallException extends ExecutionControlException {
+
+        private static final long serialVersionUID = 1L;
+
+        private final boolean[] installed;
+
+        public ClassInstallException(String message, boolean[] installed) {
+            super(message);
+            this.installed = installed;
+        }
+
+        /**
+         * Indicates which of the passed classes were successfully
+         * loaded/redefined.
+         * @return a one-to-one array with the {@link ClassBytecodes}{@code[]}
+         * array -- {@code true} if installed
+         */
+        public boolean[] installed() {
+            return installed;
+        }
+    }
+
+    /**
+     * The abstract base of of exceptions specific to running user code.
+     */
+    public static abstract class RunException extends ExecutionControlException {
+
+        private static final long serialVersionUID = 1L;
+
+        private RunException(String message) {
+            super(message);
+        }
+    }
 
     /**
-     * Queries if the class is loaded and the class bytes are current.
-     *
-     * @param classname name of the wrapper class to query
-     * @return {@code UNKNOWN} if the class is not loaded; {@code CURRENT} if
-     * the loaded/redefined bytes are equal to the most recent bytes for this
-     * wrapper class; otherwise {@code NOT_CURRENT}
+     * A 'normal' user exception occurred.
      */
-    ClassStatus getClassStatus(String classname);
+    public static class UserException extends RunException {
+
+        private static final long serialVersionUID = 1L;
+
+        private final String causeExceptionClass;
+
+        public UserException(String message, String causeExceptionClass, StackTraceElement[] stackElements) {
+            super(message);
+            this.causeExceptionClass = causeExceptionClass;
+            this.setStackTrace(stackElements);
+        }
+
+        /**
+         * Returns the class of the user exception.
+         * @return the name of the user exception class
+         */
+        public String causeExceptionClass() {
+            return causeExceptionClass;
+        }
+    }
 
     /**
-     * Interrupt a running invoke.
+     * An exception indicating that a {@code DeclarationSnippet} with unresolved
+     * references has been encountered.
+     * <p>
+     * Contrast this with the initiating {@link SPIResolutionException}
+     * (a {@code RuntimeException}) which is embedded in generated corralled
+     * code.  Also, contrast this with
+     * {@link jdk.jshell.UnresolvedReferenceException} the high-level
+     * exception (with {@code DeclarationSnippet} reference) provided in the
+     * main API.
      */
-    void stop();
+    public static class ResolutionException extends RunException {
+
+        private static final long serialVersionUID = 1L;
+
+        private final int id;
+
+        /**
+         * Constructs an exception indicating that a {@code DeclarationSnippet}
+         * with unresolved references has been encountered.
+         *
+         * @param id An internal identifier of the specific method
+         * @param stackElements the stack trace
+         */
+        public ResolutionException(int id, StackTraceElement[] stackElements) {
+            super("resolution exception: " + id);
+            this.id = id;
+            this.setStackTrace(stackElements);
+        }
+
+        /**
+         * Retrieves the internal identifier of the unresolved identifier.
+         *
+         * @return the internal identifier
+         */
+        public int id() {
+            return id;
+        }
+    }
 
     /**
-     * Returns the value of a variable.
-     *
-     * @param classname the name of the wrapper class of the variable
-     * @param varname the name of the variable
-     * @return the value of the variable
+     * An exception indicating that an
+     * {@link ExecutionControl#invoke(java.lang.String, java.lang.String) }
+     * (or theoretically a
+     * {@link ExecutionControl#varValue(java.lang.String, java.lang.String) })
+     * has been interrupted by a {@link ExecutionControl#stop() }.
      */
-    String varValue(String classname, String varname);
+    public static class StoppedException extends RunException {
+
+        private static final long serialVersionUID = 1L;
+
+        public StoppedException() {
+            super("stopped by stop()");
+        }
+    }
+
 }
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java	Wed Jul 20 23:19:09 2016 -0700
@@ -28,14 +28,11 @@
 import java.io.InputStream;
 import java.io.PrintStream;
 import java.util.List;
-import jdk.jshell.EvalException;
 import jdk.jshell.JShell;
-import jdk.jshell.UnresolvedReferenceException;
 
 /**
  * Functionality made available to a pluggable JShell execution engine.  It is
- * provided to the execution engine by the core JShell implementation calling
- * {@link ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }.
+ * provided to the execution engine by the core JShell implementation.
  * <p>
  * This interface is designed to provide the access to core JShell functionality
  * needed to implement ExecutionControl.
@@ -66,11 +63,6 @@
     PrintStream userErr();
 
     /**
-     * @return the JShell instance
-     */
-    JShell state();
-
-    /**
      * Returns the additional VM options to be used when launching the remote
      * JVM. This is advice to the execution engine.
      * <p>
@@ -81,47 +73,8 @@
     List<String> extraRemoteVMOptions();
 
     /**
-     * Retrieves the class file bytes for the specified wrapper class.
-     *
-     * @param className the name of the wrapper class
-     * @return the current class file bytes as a byte array
-     */
-    byte[] getClassBytes(String className);
-
-    /**
-     * Creates an {@code EvalException} corresponding to a user exception. An
-     * user exception thrown during
-     * {@link ExecutionControl#invoke(java.lang.String, java.lang.String) }
-     * should be converted to an {@code EvalException} using this method.
-     *
-     * @param message the exception message to use (from the user exception)
-     * @param exceptionClass the class name of the user exception
-     * @param stackElements the stack trace elements to install
-     * @return a user API EvalException for the user exception
-     */
-    EvalException createEvalException(String message, String exceptionClass,
-            StackTraceElement[] stackElements);
-
-    /**
-     * Creates an {@code UnresolvedReferenceException} for the Snippet identifed
-     * by the specified identifier. An {@link SPIResolutionException} thrown
-     * during {@link ExecutionControl#invoke(java.lang.String, java.lang.String) }
-     * should be converted to an {@code UnresolvedReferenceException} using
-     * this method.
-     * <p>
-     * The identifier is an internal id, different from the id in the API. This
-     * internal id is returned by {@link SPIResolutionException#id()}.
-     *
-     * @param id the internal integer identifier
-     * @param stackElements the stack trace elements to install
-     * @return an {@code UnresolvedReferenceException} for the unresolved
-     * reference
-     */
-    UnresolvedReferenceException createUnresolvedReferenceException(int id,
-            StackTraceElement[] stackElements);
-
-    /**
      * Reports that the execution engine has shutdown.
      */
     void closeDown();
+
 }
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/SPIResolutionException.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/SPIResolutionException.java	Wed Jul 20 23:19:09 2016 -0700
@@ -33,9 +33,6 @@
  * <p>
  * This exception is seen by the execution engine, but not seen by
  * the end user nor through the JShell API.
- *
- * @see ExecutionEnv#createUnresolvedReferenceException(int,
- * java.lang.StackTraceElement[])
  */
 @SuppressWarnings("serial")             // serialVersionUID intentionally omitted
 public class SPIResolutionException extends RuntimeException {
@@ -57,11 +54,9 @@
     }
 
     /**
-     * Retrieves the internal identifer of the unresolved identifer.
+     * Retrieves the internal identifier of the unresolved identifier.
      *
-     * @return the internal identifer
-     * @see ExecutionEnv#createUnresolvedReferenceException(int,
-     * java.lang.StackTraceElement[])
+     * @return the internal identifier
      */
     public int id() {
         return id;
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java	Wed Jul 20 23:19:09 2016 -0700
@@ -31,9 +31,9 @@
  * implementation includes a default execution engine (currently a remote
  * process which is JDI controlled).  By implementing the
  * {@link jdk.jshell.spi.ExecutionControl} interface and installing it with
- * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }
+ * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl.Generator) }
  * other execution engines can be used.
  *
- * @see jdk.jshell.execution jdk.jshell.execution for execution implementation support
+ * @see jdk.jshell.execution for execution implementation support
  */
 package jdk.jshell.spi;
--- a/langtools/test/jdk/jshell/ComputeFQNsTest.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/test/jdk/jshell/ComputeFQNsTest.java	Wed Jul 20 23:19:09 2016 -0700
@@ -76,7 +76,7 @@
         assertInferredFQNs("class X { ArrayList", "ArrayList".length(), false, "java.util.ArrayList");
     }
 
-    @Test
+    @Test(enabled = false) //TODO 8161165
     public void testSuspendIndexing() throws Throwable {
         compiler.compile(outDir, "package test; public class FQNTest { }");
         String jarName = "test.jar";
--- a/langtools/test/jdk/jshell/FailOverExecutionControlTest.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/test/jdk/jshell/FailOverExecutionControlTest.java	Wed Jul 20 23:19:09 2016 -0700
@@ -23,23 +23,20 @@
 
 /*
  * @test
- * @bug 8131029
- * @summary Test that fail-over works for FailOverExecutionControl
- * @modules jdk.jshell/jdk.internal.jshell.jdi
+ * @bug 8131029 8160127 8159935
+ * @summary Test that fail-over works for fail-over ExecutionControl generators.
+ * @modules jdk.jshell/jdk.jshell.execution
  *          jdk.jshell/jdk.jshell.spi
  * @build KullaTesting ExecutionControlTestBase
  * @run testng FailOverExecutionControlTest
  */
 
-
-import java.util.Collection;
 import org.testng.annotations.Test;
 import org.testng.annotations.BeforeMethod;
-import jdk.internal.jshell.jdi.FailOverExecutionControl;
-import jdk.internal.jshell.jdi.JDIExecutionControl;
-import jdk.jshell.JShellException;
+import jdk.jshell.execution.JDIDefaultExecutionControl;
 import jdk.jshell.spi.ExecutionControl;
 import jdk.jshell.spi.ExecutionEnv;
+import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
 
 @Test
 public class FailOverExecutionControlTest extends ExecutionControlTestBase {
@@ -47,56 +44,16 @@
     @BeforeMethod
     @Override
     public void setUp() {
-        setUp(new FailOverExecutionControl(
-                new AlwaysFailingExecutionControl(),
-                new AlwaysFailingExecutionControl(),
-                new JDIExecutionControl()));
+        setUp(builder -> builder.executionEngine(failOverExecutionControlGenerator(
+                new AlwaysFailingGenerator(),
+                new AlwaysFailingGenerator(),
+                JDIDefaultExecutionControl.launch())));
     }
 
-    class AlwaysFailingExecutionControl implements ExecutionControl {
-
-        @Override
-        public void start(ExecutionEnv env) throws Exception {
-            throw new UnsupportedOperationException("This operation intentionally broken.");
-        }
-
-        @Override
-        public void close() {
-            throw new UnsupportedOperationException("This operation intentionally broken.");
-        }
-
-        @Override
-        public boolean addToClasspath(String path) {
-            throw new UnsupportedOperationException("This operation intentionally broken.");
-        }
+    class AlwaysFailingGenerator implements ExecutionControl.Generator {
 
         @Override
-        public String invoke(String classname, String methodname) throws JShellException {
-            throw new UnsupportedOperationException("This operation intentionally broken.");
-        }
-
-        @Override
-        public boolean load(Collection<String> classes) {
-            throw new UnsupportedOperationException("This operation intentionally broken.");
-        }
-
-        @Override
-        public boolean redefine(Collection<String> classes) {
-            throw new UnsupportedOperationException("This operation intentionally broken.");
-        }
-
-        @Override
-        public ClassStatus getClassStatus(String classname) {
-            throw new UnsupportedOperationException("This operation intentionally broken.");
-        }
-
-        @Override
-        public void stop() {
-            throw new UnsupportedOperationException("This operation intentionally broken.");
-        }
-
-        @Override
-        public String varValue(String classname, String varname) {
+        public ExecutionControl generate(ExecutionEnv env) throws UnsupportedOperationException {
             throw new UnsupportedOperationException("This operation intentionally broken.");
         }
 
--- a/langtools/test/jdk/jshell/JDIListeningExecutionControlTest.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/test/jdk/jshell/JDIListeningExecutionControlTest.java	Wed Jul 20 23:19:09 2016 -0700
@@ -23,9 +23,9 @@
 
 /*
  * @test
- * @bug 8131029
+ * @bug 8131029 8159935 8160127
  * @summary Tests for alternate JDI connector -- listening
- * @modules jdk.jshell/jdk.internal.jshell.jdi
+ * @modules jdk.jshell/jdk.jshell.execution
  * @build KullaTesting ExecutionControlTestBase
  * @run testng JDIListeningExecutionControlTest
  */
@@ -33,7 +33,7 @@
 
 import org.testng.annotations.Test;
 import org.testng.annotations.BeforeMethod;
-import jdk.internal.jshell.jdi.JDIExecutionControl;
+import jdk.jshell.execution.JDIDefaultExecutionControl;
 
 @Test
 public class JDIListeningExecutionControlTest extends ExecutionControlTestBase {
@@ -41,6 +41,6 @@
     @BeforeMethod
     @Override
     public void setUp() {
-        setUp(new JDIExecutionControl(false));
+        setUp(builder -> builder.executionEngine(JDIDefaultExecutionControl.listen()));
     }
 }
--- a/langtools/test/jdk/jshell/KullaTesting.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/test/jdk/jshell/KullaTesting.java	Wed Jul 20 23:19:09 2016 -0700
@@ -73,7 +73,6 @@
 import static jdk.jshell.Snippet.Status.*;
 import static org.testng.Assert.*;
 import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND;
-import jdk.jshell.spi.ExecutionControl;
 
 public class KullaTesting {
 
@@ -158,10 +157,6 @@
         setUp(b -> {});
     }
 
-    public void setUp(ExecutionControl ec) {
-        setUp(b -> b.executionEngine(ec));
-    }
-
     public void setUp(Consumer<JShell.Builder> bc) {
         inStream = new TestingInputStream();
         outStream = new ByteArrayOutputStream();
--- a/langtools/test/jdk/jshell/LocalExecutionControl.java	Tue Jul 19 11:27:56 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,313 +0,0 @@
-/*
- * Copyright (c) 2016, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-import jdk.jshell.spi.ExecutionControl;
-import jdk.jshell.spi.ExecutionEnv;
-import jdk.jshell.spi.SPIResolutionException;
-import jdk.jshell.EvalException;
-import jdk.jshell.UnresolvedReferenceException;
-
-import java.io.File;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.security.CodeSource;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * An implementation of ExecutionControl which executes in the same JVM as the
- * JShell core.
- *
- * @author Grigory Ptashko
- */
-class LocalExecutionControl implements ExecutionControl {
-    private class REPLClassLoader extends URLClassLoader {
-        REPLClassLoader() {
-            super(new URL[0]);
-        }
-
-        @Override
-        protected Class<?> findClass(String name) throws ClassNotFoundException {
-            debug("findClass %s\n", name);
-            byte[] b = execEnv.getClassBytes(name);
-            if (b == null) {
-                return super.findClass(name);
-            }
-            return super.defineClass(name, b, 0, b.length, (CodeSource)null);
-        }
-
-        @Override
-        public void addURL(URL url) {
-            super.addURL(url);
-        }
-    }
-
-    private ExecutionEnv execEnv;
-    private final Object STOP_LOCK = new Object();
-    private boolean userCodeRunning = false;
-    private REPLClassLoader loader = new REPLClassLoader();
-    private final Map<String, Class<?>> klasses = new TreeMap<>();
-    private final Map<String, byte[]> classBytes = new HashMap<>();
-    private ThreadGroup execThreadGroup;
-
-    @Override
-    public void start(ExecutionEnv execEnv) throws Exception {
-        this.execEnv = execEnv;
-
-        debug("Process-local code snippets execution control started");
-    }
-
-    @Override
-    public void close() {
-    }
-
-    @Override
-    public boolean load(Collection<String> classes) {
-        try {
-            loadLocal(classes);
-
-            return true;
-        } catch (ClassNotFoundException | ClassCastException ex) {
-            debug(ex, "Exception on load operation");
-        }
-
-        return false;
-    }
-
-    @Override
-    public String invoke(String classname, String methodname) throws EvalException, UnresolvedReferenceException {
-        try {
-            synchronized (STOP_LOCK) {
-                userCodeRunning = true;
-            }
-
-            // Invoke executable entry point in loaded code
-            Class<?> klass = klasses.get(classname);
-            if (klass == null) {
-                debug("Invoke failure: no such class loaded %s\n", classname);
-
-                return "";
-            }
-
-            Method doitMethod;
-            try {
-                this.getClass().getModule().addReads(klass.getModule());
-                this.getClass().getModule().addExports(SPIResolutionException.class.getPackage()
-                        .getName(), klass.getModule());
-                doitMethod = klass.getDeclaredMethod(methodname, new Class<?>[0]);
-                doitMethod.setAccessible(true);
-
-                execThreadGroup = new ThreadGroup("JShell process local execution");
-
-                AtomicReference<InvocationTargetException> iteEx = new AtomicReference<>();
-                AtomicReference<IllegalAccessException> iaeEx = new AtomicReference<>();
-                AtomicReference<NoSuchMethodException> nmeEx = new AtomicReference<>();
-                AtomicReference<Boolean> stopped = new AtomicReference<>(false);
-
-                Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
-                    if (e instanceof InvocationTargetException) {
-                        if (e.getCause() instanceof ThreadDeath) {
-                            stopped.set(true);
-                        } else {
-                            iteEx.set((InvocationTargetException)e);
-                        }
-                    } else if (e instanceof IllegalAccessException) {
-                        iaeEx.set((IllegalAccessException)e);
-                    } else if (e instanceof NoSuchMethodException) {
-                        nmeEx.set((NoSuchMethodException)e);
-                    } else if (e instanceof ThreadDeath) {
-                        stopped.set(true);
-                    }
-                });
-
-                final Object[] res = new Object[1];
-                Thread snippetThread = new Thread(execThreadGroup, () -> {
-                    try {
-                        res[0] = doitMethod.invoke(null, new Object[0]);
-                    } catch (InvocationTargetException e) {
-                        if (e.getCause() instanceof ThreadDeath) {
-                            stopped.set(true);
-                        } else {
-                            iteEx.set(e);
-                        }
-                    } catch (IllegalAccessException e) {
-                        iaeEx.set(e);
-                    } catch (ThreadDeath e) {
-                        stopped.set(true);
-                    }
-                });
-
-                snippetThread.start();
-                Thread[] threadList = new Thread[execThreadGroup.activeCount()];
-                execThreadGroup.enumerate(threadList);
-                for (Thread thread : threadList) {
-                    if (thread != null)
-                        thread.join();
-                }
-
-                if (stopped.get()) {
-                    debug("Killed.");
-
-                    return "";
-                }
-
-                if (iteEx.get() != null) {
-                    throw iteEx.get();
-                } else if (nmeEx.get() != null) {
-                    throw nmeEx.get();
-                } else if (iaeEx.get() != null) {
-                    throw iaeEx.get();
-                }
-
-                return valueString(res[0]);
-            } catch (InvocationTargetException ex) {
-                Throwable cause = ex.getCause();
-                StackTraceElement[] elems = cause.getStackTrace();
-                if (cause instanceof SPIResolutionException) {
-                    int id = ((SPIResolutionException)cause).id();
-
-                    throw execEnv.createUnresolvedReferenceException(id, elems);
-                } else {
-                    throw execEnv.createEvalException(cause.getMessage() == null ?
-                            "<none>" : cause.getMessage(), cause.getClass().getName(), elems);
-                }
-            } catch (NoSuchMethodException | IllegalAccessException | InterruptedException ex) {
-                debug(ex, "Invoke failure");
-            }
-        } finally {
-            synchronized (STOP_LOCK) {
-                userCodeRunning = false;
-            }
-        }
-
-        return "";
-    }
-
-    @Override
-    @SuppressWarnings("deprecation")
-    public void stop() {
-        synchronized (STOP_LOCK) {
-            if (!userCodeRunning)
-                return;
-
-            if (execThreadGroup == null) {
-                debug("Process-local code snippets thread group is null. Aborting stop.");
-
-                return;
-            }
-
-            execThreadGroup.stop();
-        }
-    }
-
-    @Override
-    public String varValue(String classname, String varname) {
-        Class<?> klass = klasses.get(classname);
-        if (klass == null) {
-            debug("Var value failure: no such class loaded %s\n", classname);
-
-            return "";
-        }
-        try {
-            this.getClass().getModule().addReads(klass.getModule());
-            Field var = klass.getDeclaredField(varname);
-            var.setAccessible(true);
-            Object res = var.get(null);
-
-            return valueString(res);
-        } catch (Exception ex) {
-            debug("Var value failure: no such field %s.%s\n", classname, varname);
-        }
-
-        return "";
-    }
-
-    @Override
-    public boolean addToClasspath(String cp) {
-        // Append to the claspath
-        for (String path : cp.split(File.pathSeparator)) {
-            try {
-                loader.addURL(new File(path).toURI().toURL());
-            } catch (MalformedURLException e) {
-                throw new InternalError("Classpath addition failed: " + cp, e);
-            }
-        }
-
-        return true;
-    }
-
-    @Override
-    public boolean redefine(Collection<String> classes) {
-        return false;
-    }
-
-    @Override
-    public ClassStatus getClassStatus(String classname) {
-        if (!classBytes.containsKey(classname)) {
-            return ClassStatus.UNKNOWN;
-        } else if (!Arrays.equals(classBytes.get(classname), execEnv.getClassBytes(classname))) {
-            return ClassStatus.NOT_CURRENT;
-        } else {
-            return ClassStatus.CURRENT;
-        }
-    }
-
-    private void loadLocal(Collection<String> classes) throws ClassNotFoundException {
-        for (String className : classes) {
-            Class<?> klass = loader.loadClass(className);
-            klasses.put(className, klass);
-            classBytes.put(className, execEnv.getClassBytes(className));
-            klass.getDeclaredMethods();
-        }
-    }
-
-    private void debug(String format, Object... args) {
-        //debug(execEnv.state(), execEnv.userErr(), flags, format, args);
-    }
-
-    private void debug(Exception ex, String where) {
-        //debug(execEnv.state(), execEnv.userErr(), ex, where);
-    }
-
-    private static String valueString(Object value) {
-        if (value == null) {
-            return "null";
-        } else if (value instanceof String) {
-            return "\"" + (String)value + "\"";
-        } else if (value instanceof Character) {
-            return "'" + value + "'";
-        } else {
-            return value.toString();
-        }
-    }
-}
--- a/langtools/test/jdk/jshell/UserExecutionControlTest.java	Tue Jul 19 11:27:56 2016 -0700
+++ b/langtools/test/jdk/jshell/UserExecutionControlTest.java	Wed Jul 20 23:19:09 2016 -0700
@@ -23,13 +23,14 @@
 
 /*
  * @test
- * @bug 8156101
+ * @bug 8156101 8159935 8159122
  * @summary Tests for ExecutionControl SPI
- * @build KullaTesting LocalExecutionControl ExecutionControlTestBase
+ * @build KullaTesting ExecutionControlTestBase
  * @run testng UserExecutionControlTest
  */
 
 
+import jdk.jshell.execution.LocalExecutionControl;
 import org.testng.annotations.Test;
 import static org.testng.Assert.assertEquals;
 import org.testng.annotations.BeforeMethod;
@@ -40,7 +41,7 @@
     @BeforeMethod
     @Override
     public void setUp() {
-        setUp(new LocalExecutionControl());
+        setUp(builder -> builder.executionEngine(LocalExecutionControl.create()));
     }
 
     public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/UserJDIUserRemoteTest.java	Wed Jul 20 23:19:09 2016 -0700
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2016, 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.
+ */
+
+ /*
+ * @test
+ * @bug 8160128 8159935
+ * @summary Tests for Aux channel, custom remote agents, custom JDI implementations.
+ * @build KullaTesting ExecutionControlTestBase
+ * @run testng UserJDIUserRemoteTest
+ */
+import java.io.ByteArrayOutputStream;
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+import jdk.jshell.Snippet;
+import static jdk.jshell.Snippet.Status.OVERWRITTEN;
+import static jdk.jshell.Snippet.Status.VALID;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.List;
+import com.sun.jdi.VMDisconnectedException;
+import com.sun.jdi.VirtualMachine;
+import jdk.jshell.VarSnippet;
+import jdk.jshell.execution.DirectExecutionControl;
+import jdk.jshell.execution.JDIExecutionControl;
+import jdk.jshell.execution.JDIInitiator;
+import jdk.jshell.execution.Util;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.Socket;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import jdk.jshell.spi.ExecutionControl;
+import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
+import jdk.jshell.spi.ExecutionEnv;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+import static jdk.jshell.execution.Util.forwardExecutionControlAndIO;
+import static jdk.jshell.execution.Util.remoteInput;
+
+@Test
+public class UserJDIUserRemoteTest extends ExecutionControlTestBase {
+
+    ExecutionControl currentEC;
+    ByteArrayOutputStream auxStream;
+
+    @BeforeMethod
+    @Override
+    public void setUp() {
+        auxStream = new ByteArrayOutputStream();
+        setUp(builder -> builder.executionEngine(MyExecutionControl.create(this)));
+    }
+
+    public void testVarValue() {
+        VarSnippet dv = varKey(assertEval("double aDouble = 1.5;"));
+        String vd = getState().varValue(dv);
+        assertEquals(vd, "1.5");
+        assertEquals(auxStream.toString(), "aDouble");
+    }
+
+    public void testExtension() throws ExecutionControlException {
+        assertEval("42;");
+        Object res = currentEC.extensionCommand("FROG", "test");
+        assertEquals(res, "ribbit");
+    }
+
+    public void testRedefine() {
+        Snippet vx = varKey(assertEval("int x;"));
+        Snippet mu = methodKey(assertEval("int mu() { return x * 4; }"));
+        Snippet c = classKey(assertEval("class C { String v() { return \"#\" + mu(); } }"));
+        assertEval("C c0  = new C();");
+        assertEval("c0.v();", "\"#0\"");
+        assertEval("int x = 10;", "10",
+                ste(MAIN_SNIPPET, VALID, VALID, false, null),
+                ste(vx, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+        assertEval("c0.v();", "\"#40\"");
+        assertEval("C c = new C();");
+        assertEval("c.v();", "\"#40\"");
+        assertEval("int mu() { return x * 3; }",
+                ste(MAIN_SNIPPET, VALID, VALID, false, null),
+                ste(mu, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+        assertEval("c.v();", "\"#30\"");
+        assertEval("class C { String v() { return \"@\" + mu(); } }",
+                ste(MAIN_SNIPPET, VALID, VALID, false, null),
+                ste(c, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+        assertEval("c0.v();", "\"@30\"");
+        assertEval("c = new C();");
+        assertEval("c.v();", "\"@30\"");
+        assertActiveKeys();
+    }
+}
+
+class MyExecutionControl extends JDIExecutionControl {
+
+    private static final String REMOTE_AGENT = MyRemoteExecutionControl.class.getName();
+
+    private VirtualMachine vm;
+    private Process process;
+
+    /**
+     * Creates an ExecutionControl instance based on a JDI
+     * {@code LaunchingConnector}.
+     *
+     * @return the generator
+     */
+    public static ExecutionControl.Generator create(UserJDIUserRemoteTest test) {
+        return env -> make(env, test);
+    }
+
+    /**
+     * Creates an ExecutionControl instance based on a JDI
+     * {@code ListeningConnector} or {@code LaunchingConnector}.
+     *
+     * Initialize JDI and use it to launch the remote JVM. Set-up a socket for
+     * commands and results. This socket also transports the user
+     * input/output/error.
+     *
+     * @param env the context passed by
+         * {@link jdk.jshell.spi.ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }
+     * @return the channel
+     * @throws IOException if there are errors in set-up
+     */
+    static MyExecutionControl make(ExecutionEnv env, UserJDIUserRemoteTest test) throws IOException {
+        try (final ServerSocket listener = new ServerSocket(0)) {
+            // timeout after 60 seconds
+            listener.setSoTimeout(60000);
+            int port = listener.getLocalPort();
+
+            // Set-up the JDI connection
+            List<String> opts = new ArrayList<>(env.extraRemoteVMOptions());
+            opts.add("-classpath");
+            opts.add(System.getProperty("java.class.path")
+                    + System.getProperty("path.separator")
+                    + System.getProperty("user.dir"));
+            JDIInitiator jdii = new JDIInitiator(port,
+                    opts, REMOTE_AGENT, true);
+            VirtualMachine vm = jdii.vm();
+            Process process = jdii.process();
+
+            List<Consumer<String>> deathListeners = new ArrayList<>();
+            deathListeners.add(s -> env.closeDown());
+            Util.detectJDIExitEvent(vm, s -> {
+                for (Consumer<String> h : deathListeners) {
+                    h.accept(s);
+                }
+            });
+
+            // Set-up the commands/reslts on the socket.  Piggy-back snippet
+            // output.
+            Socket socket = listener.accept();
+            // out before in -- match remote creation so we don't hang
+            ObjectOutput cmdout = new ObjectOutputStream(socket.getOutputStream());
+            Map<String, OutputStream> io = new HashMap<>();
+            io.put("out", env.userOut());
+            io.put("err", env.userErr());
+            io.put("aux", test.auxStream);
+            ObjectInput cmdin = remoteInput(socket.getInputStream(), io);
+            MyExecutionControl myec = new MyExecutionControl(cmdout, cmdin, vm, process, deathListeners);
+            test.currentEC = myec;
+            return myec;
+        }
+    }
+
+    /**
+     * Create an instance.
+     *
+     * @param out the output for commands
+     * @param in the input for responses
+     */
+    private MyExecutionControl(ObjectOutput out, ObjectInput in,
+            VirtualMachine vm, Process process,
+            List<Consumer<String>> deathListeners) {
+        super(out, in);
+        this.vm = vm;
+        this.process = process;
+        deathListeners.add(s -> disposeVM());
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        disposeVM();
+    }
+
+    private synchronized void disposeVM() {
+        try {
+            if (vm != null) {
+                vm.dispose(); // This could NPE, so it is caught below
+                vm = null;
+            }
+        } catch (VMDisconnectedException ex) {
+            // Ignore if already closed
+        } catch (Throwable e) {
+            fail("disposeVM threw: " + e);
+        } finally {
+            if (process != null) {
+                process.destroy();
+                process = null;
+            }
+        }
+    }
+
+    @Override
+    protected synchronized VirtualMachine vm() throws EngineTerminationException {
+        if (vm == null) {
+            throw new EngineTerminationException("VM closed");
+        } else {
+            return vm;
+        }
+    }
+
+}
+
+class MyRemoteExecutionControl extends DirectExecutionControl implements ExecutionControl {
+
+    static PrintStream auxPrint;
+
+    /**
+     * Launch the agent, connecting to the JShell-core over the socket specified
+     * in the command-line argument.
+     *
+     * @param args standard command-line arguments, expectation is the socket
+     * number is the only argument
+     * @throws Exception any unexpected exception
+     */
+    public static void main(String[] args) throws Exception {
+        try {
+            String loopBack = null;
+            Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
+            InputStream inStream = socket.getInputStream();
+            OutputStream outStream = socket.getOutputStream();
+            Map<String, Consumer<OutputStream>> chans = new HashMap<>();
+            chans.put("out", st -> System.setOut(new PrintStream(st, true)));
+            chans.put("err", st -> System.setErr(new PrintStream(st, true)));
+            chans.put("aux", st -> { auxPrint = new PrintStream(st, true); });
+            forwardExecutionControlAndIO(new MyRemoteExecutionControl(), inStream, outStream, chans);
+        } catch (Throwable ex) {
+            throw ex;
+        }
+    }
+
+    @Override
+    public String varValue(String className, String varName)
+            throws RunException, EngineTerminationException, InternalException {
+        auxPrint.print(varName);
+        return super.varValue(className, varName);
+    }
+
+    @Override
+    public Object extensionCommand(String className, Object arg)
+            throws RunException, EngineTerminationException, InternalException {
+        if (!arg.equals("test")) {
+            throw new InternalException("expected extensionCommand arg to be 'test' got: " + arg);
+        }
+        return "ribbit";
+    }
+
+}