8156101: JShell SPI: Provide a pluggable execution control SPI
authorrfield
Sat, 21 May 2016 22:32:08 -0700
changeset 38535 4a25025e0b0d
parent 38534 425b30506f80
child 38536 42569f7fe4e6
8156101: JShell SPI: Provide a pluggable execution control SPI Reviewed-by: jlahoda
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/debug/InternalDebugControl.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/ClassTracker.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIConnection.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIEnv.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/RemoteCodes.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteResolutionException.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/ClassTracker.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/Corraller.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/ExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIConnection.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIEnv.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIEventHandler.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/JDINotConnectedException.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrap.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrapMap.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.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/Util.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/Wrap.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/src/jdk.jshell/share/classes/module-info.java
langtools/test/jdk/jshell/ExecutionControlTest.java
langtools/test/jdk/jshell/KullaTesting.java
langtools/test/jdk/jshell/LocalExecutionControl.java
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/debug/InternalDebugControl.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/debug/InternalDebugControl.java	Sat May 21 22:32:08 2016 -0700
@@ -23,24 +23,60 @@
 
 package jdk.internal.jshell.debug;
 
+import java.io.PrintStream;
 import java.util.HashMap;
 import java.util.Map;
 import jdk.jshell.JShell;
 
 /**
- * Used to externally control output messages for debugging the implementation
- * of the JShell API.  This is NOT a supported interface,
- * @author Robert Field
+/**
+ * This class is used to externally control output messages for debugging the
+ * implementation of the JShell API.
+ * <p>
+ * This is not part of the SPI, not API.
  */
 public class InternalDebugControl {
-    public static final int DBG_GEN   = 0b0000001;
-    public static final int DBG_FMGR  = 0b0000010;
+
+    /**
+     * This is a static only class; The constructor should never be called.
+     */
+    private InternalDebugControl() {
+    }
+
+    /**
+     * General debugging.
+     */
+    public static final int DBG_GEN = 0b0000001;
+
+    /**
+     * File manager debuging.
+     */
+    public static final int DBG_FMGR = 0b0000010;
+
+    /**
+     * Completion analysis debugging.
+     */
     public static final int DBG_COMPA = 0b0000100;
-    public static final int DBG_DEP   = 0b0001000;
-    public static final int DBG_EVNT  = 0b0010000;
+
+    /**
+     * Dependency debugging.
+     */
+    public static final int DBG_DEP = 0b0001000;
+
+    /**
+     * Event debugging.
+     */
+    public static final int DBG_EVNT = 0b0010000;
 
     private static Map<JShell, Integer> debugMap = null;
 
+    /**
+     * Sets which debug flags are enabled for a given JShell instance. The flags
+     * are or'ed bits as defined in {@code DBG_*}.
+     *
+     * @param state the JShell instance
+     * @param flags the or'ed debug bits
+     */
     public static void setDebugFlags(JShell state, int flags) {
         if (debugMap == null) {
             debugMap = new HashMap<>();
@@ -48,7 +84,14 @@
         debugMap.put(state, flags);
     }
 
-    public static boolean debugEnabled(JShell state, int flag) {
+    /**
+     * Tests if any of the specified debug flags are enabled.
+     *
+     * @param state the JShell instance
+     * @param flag the {@code DBG_*} bits to check
+     * @return true if any of the flags are enabled
+     */
+    public static boolean isDebugEnabled(JShell state, int flag) {
         if (debugMap == null) {
             return false;
         }
@@ -58,4 +101,34 @@
         }
         return (flags & flag) != 0;
     }
+
+    /**
+     * Displays debug info if the specified debug flags are enabled.
+     *
+     * @param state the current JShell instance
+     * @param err the {@code PrintStream} to report on
+     * @param flags {@code DBG_*} flag bits to check
+     * @param format format string for the output
+     * @param args args for the format string
+     */
+    public static void debug(JShell state, PrintStream err, int flags, String format, Object... args) {
+        if (isDebugEnabled(state, flags)) {
+            err.printf(format, args);
+        }
+    }
+
+    /**
+     * Displays a fatal exception as debug info.
+     *
+     * @param state the current JShell instance
+     * @param err the {@code PrintStream} to report on
+     * @param ex the fatal Exception
+     * @param where additional context
+     */
+    public static void debug(JShell state, PrintStream err, Exception ex, String where) {
+        if (isDebugEnabled(state, 0xFFFFFFFF)) {
+            err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
+            ex.printStackTrace(err);
+        }
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/ClassTracker.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,129 @@
+/*
+ * 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;
+
+/**
+ * Tracks the state of a class.
+ */
+class ClassTracker {
+
+    private final JDIEnv jdiEnv;
+    private final HashMap<String, ClassInfo> map;
+
+    ClassTracker(JDIEnv jdiEnv) {
+        this.jdiEnv = jdiEnv;
+        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 = jdiEnv.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));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIConnection.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,345 @@
+/*
+ * 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 VirtualMachine vm;
+    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;
+
+    synchronized void notifyOutputComplete() {
+        outputCompleteCount++;
+        notifyAll();
+    }
+
+    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;
+    }
+
+
+    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;
+    }
+
+    synchronized VirtualMachine open() {
+        if (connector instanceof LaunchingConnector) {
+            vm = launchTarget();
+        } else if (connector instanceof AttachingConnector) {
+            vm = attachTarget();
+        } else if (connector instanceof ListeningConnector) {
+            vm = listenTarget();
+        } else {
+            throw new InternalError("Invalid connect type");
+        }
+        vm.setDebugTraceMode(traceFlags);
+        // Uncomment here and below to enable event requests
+        // installEventRequests(vm);
+
+        return vm;
+    }
+
+    synchronized boolean setConnectorArg(String name, String value) {
+        /*
+         * Too late if the connection already made
+         */
+        if (vm != null) {
+            return false;
+        }
+
+        Connector.Argument argument = connectorArgs.get(name);
+        if (argument == null) {
+            return false;
+        }
+        argument.setValue(value);
+        return true;
+    }
+
+    String connectorArg(String name) {
+        Connector.Argument argument = connectorArgs.get(name);
+        if (argument == null) {
+            return "";
+        }
+        return argument.value();
+    }
+
+    public synchronized VirtualMachine vm() {
+        if (vm == null) {
+            throw new JDINotConnectedException();
+        } else {
+            return vm;
+        }
+    }
+
+    synchronized boolean isOpen() {
+        return (vm != null);
+    }
+
+    boolean isLaunch() {
+        return (connector instanceof LaunchingConnector);
+    }
+
+    synchronized boolean isRunning() {
+        return process != null && process.isAlive();
+    }
+
+    public 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
+        } finally {
+            if (process != null) {
+                process.destroy();
+                process = null;
+            }
+            waitOutputComplete();
+        }
+    }
+
+/*** Preserved for possible future support of event requests
+
+    private void installEventRequests(VirtualMachine vm) {
+        if (vm.canBeModified()){
+            setEventRequests(vm);
+            resolveEventRequests();
+        }
+    }
+
+    private void setEventRequests(VirtualMachine vm) {
+        EventRequestManager erm = vm.eventRequestManager();
+
+        // Normally, we want all uncaught exceptions.  We request them
+        // via the same mechanism as Commands.commandCatchException()
+        // so the user can ignore them later if they are not
+        // interested.
+        // FIXME: this works but generates spurious messages on stdout
+        //        during startup:
+        //          Set uncaught java.lang.Throwable
+        //          Set deferred uncaught java.lang.Throwable
+        Commands evaluator = new Commands();
+        evaluator.commandCatchException
+            (new StringTokenizer("uncaught java.lang.Throwable"));
+
+        ThreadStartRequest tsr = erm.createThreadStartRequest();
+        tsr.enable();
+        ThreadDeathRequest tdr = erm.createThreadDeathRequest();
+        tdr.enable();
+    }
+
+    private void resolveEventRequests() {
+        Env.specList.resolveAll();
+    }
+***/
+
+    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) {
+                pStream.print((char) i);
+            }
+        } catch (IOException ex) {
+            String s = ex.getMessage();
+            if (!s.startsWith("Bad file number")) {
+                throw ex;
+            }
+            // else 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.jdiEnv.shutdown();
+                } 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.jdiEnv.shutdown();
+                }
+            }
+        };
+        thr.setPriority(Thread.MAX_PRIORITY-1);
+        thr.start();
+    }
+
+    /* launch child target vm */
+    private VirtualMachine launchTarget() {
+        LaunchingConnector launcher = (LaunchingConnector)connector;
+        try {
+            VirtualMachine new_vm = launcher.launch(connectorArgs);
+            process = new_vm.process();
+            displayRemoteOutput(process.getErrorStream(), ec.execEnv.userErr());
+            displayRemoteOutput(process.getInputStream(), ec.execEnv.userOut());
+            readRemoteInput(process.getOutputStream(), ec.execEnv.userIn());
+            return new_vm;
+        } catch (Exception ex) {
+            reportLaunchFail(ex, "launch");
+        }
+        return null;
+    }
+
+    /* JShell currently uses only launch, preserved for futures: */
+    /* attach to running target vm */
+    private VirtualMachine attachTarget() {
+        AttachingConnector attacher = (AttachingConnector)connector;
+        try {
+            return attacher.attach(connectorArgs);
+        } catch (Exception ex) {
+            reportLaunchFail(ex, "attach");
+        }
+        return null;
+    }
+
+    /* JShell currently uses only launch, preserved for futures: */
+    /* listen for connection from target vm */
+    private VirtualMachine listenTarget() {
+        ListeningConnector listener = (ListeningConnector)connector;
+        try {
+            String retAddress = listener.startListening(connectorArgs);
+            ec.debug(DBG_GEN, "Listening at address: " + retAddress);
+            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);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIEnv.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 1998, 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 java.util.Map;
+
+import com.sun.jdi.*;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
+
+/**
+ * Representation of a Java Debug Interface environment
+ * Select methods extracted from jdb Env; shutdown() adapted to JShell shutdown.
+ */
+class JDIEnv {
+
+    private JDIConnection connection;
+    private final JDIExecutionControl ec;
+
+    JDIEnv(JDIExecutionControl ec) {
+        this.ec = ec;
+    }
+
+    void init(String connectorName, Map<String, String> argumentName2Value, boolean openNow, int flags) {
+        connection = new JDIConnection(ec, connectorName, argumentName2Value, flags);
+        if (!connection.isLaunch() || openNow) {
+            connection.open();
+        }
+    }
+
+    JDIConnection connection() {
+        return connection;
+    }
+
+    VirtualMachine vm() {
+        return connection.vm();
+    }
+
+    void shutdown() {
+        if (connection != null) {
+            try {
+                connection.disposeVM();
+            } catch (VMDisconnectedException e) {
+                // Shutting down after the VM has gone away. This is
+                // not an error, and we just ignore it.
+            } catch (Throwable e) {
+                ec.debug(DBG_GEN, null, "disposeVM threw: " + e);
+            }
+        }
+        if (ec.execEnv.state() != null) { // If state has been set-up
+            try {
+                ec.execEnv.closeDown();
+            } catch (Throwable e) {
+                ec.debug(DBG_GEN, null, "state().closeDown() threw: " + e);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIEventHandler.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,181 @@
+/*
+ * 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 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 {
+
+    Thread thread;
+    volatile boolean connected = true;
+    boolean completed = false;
+    String shutdownMessageKey;
+    final JDIEnv env;
+
+    JDIEventHandler(JDIEnv env) {
+        this.env = env;
+        this.thread = new Thread(this, "event-handler");
+        this.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 = env.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;
+            shutdownMessageKey = "The application exited";
+        } else if (event instanceof VMDisconnectEvent) {
+            connected = false;
+            if (!vmDied) {
+                shutdownMessageKey = "The application has been disconnected";
+            }
+        } else {
+            throw new InternalError("Unexpected event type: " +
+                    event.getClass());
+        }
+        env.shutdown();
+    }
+
+    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 = env.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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDIExecutionControl.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,595 @@
+/*
+ * 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 com.sun.jdi.*;
+import java.io.EOFException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+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;
+    JDIEnv jdiEnv;
+    private ClassTracker tracker;
+    private JDIEventHandler handler;
+    private Socket socket;
+    private ObjectInputStream remoteIn;
+    private ObjectOutputStream remoteOut;
+    private String remoteVMOptions;
+
+    /**
+     * 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;
+        this.jdiEnv = new JDIEnv(this);
+        this.tracker = new ClassTracker(jdiEnv);
+        StringBuilder sb = new StringBuilder();
+        execEnv.extraRemoteVMOptions().stream()
+                .forEach(s -> {
+                    sb.append(" ");
+                    sb.append(s);
+                });
+        this.remoteVMOptions = sb.toString();
+        try (ServerSocket listener = new ServerSocket(0)) {
+            // timeout after 60 seconds
+            listener.setSoTimeout(60000);
+            int port = listener.getLocalPort();
+            jdiGo(port);
+            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 (remoteOut != null) {
+                remoteOut.writeInt(CMD_EXIT);
+                remoteOut.flush();
+            }
+            JDIConnection c = jdiEnv.connection();
+            if (c != null) {
+                c.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 (!jdiEnv.connection().isRunning()) {
+                // The JDI connection is no longer live, shutdown.
+                jdiEnv.shutdown();
+            } 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) {
+            jdiEnv.shutdown();
+        } 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.
+            jdiEnv.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;
+        }
+    }
+
+    /**
+     * 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 -> tracker.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 = tracker.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;
+    }
+
+    /**
+     * Launch the remote agent as a JDI connection.
+     *
+     * @param port the socket port for (non-JDI) commands
+     */
+    private void jdiGo(int port) {
+        //MessageOutput.textResources = ResourceBundle.getBundle("impl.TTYResources",
+        //        Locale.getDefault());
+
+        // Set-up for a fresh launch of a remote agent with any user-specified VM options.
+        String connectorName = "com.sun.jdi.CommandLineLaunch";
+        Map<String, String> argumentName2Value = new HashMap<>();
+        argumentName2Value.put("main", "jdk.internal.jshell.remote.RemoteAgent " + port);
+        argumentName2Value.put("options", remoteVMOptions);
+
+        boolean launchImmediately = true;
+        int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
+
+        // Launch.
+        jdiEnv.init(connectorName, argumentName2Value, launchImmediately, traceFlags);
+
+        if (jdiEnv.connection().isOpen() && jdiEnv.vm().canBeModified()) {
+            /*
+             * Connection opened on startup. Start event handler
+             * immediately, telling it (through arg 2) to stop on the
+             * VM start event.
+             */
+            handler = new JDIEventHandler(jdiEnv);
+        }
+    }
+
+    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 = handler.env.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();
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/jdi/JDINotConnectedException.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,43 @@
+/*
+ * 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	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteAgent.java	Sat May 21 22:32:08 2016 -0700
@@ -24,6 +24,7 @@
  */
 
 package jdk.internal.jshell.remote;
+import jdk.jshell.spi.SPIResolutionException;
 import java.io.File;
 import java.io.IOException;
 import java.io.ObjectInputStream;
@@ -111,10 +112,11 @@
                         out.flush();
                         break;
                     }
+                    String methodName = in.readUTF();
                     Method doitMethod;
                     try {
-                        this.getClass().getModule().addExports(RemoteResolutionException.class.getPackage().getName(), klass.getModule());
-                        doitMethod = klass.getDeclaredMethod(DOIT_METHOD_NAME, new Class<?>[0]);
+                        this.getClass().getModule().addExports(SPIResolutionException.class.getPackage().getName(), klass.getModule());
+                        doitMethod = klass.getDeclaredMethod(methodName, new Class<?>[0]);
                         doitMethod.setAccessible(true);
                         Object res;
                         try {
@@ -138,9 +140,9 @@
                     } catch (InvocationTargetException ex) {
                         Throwable cause = ex.getCause();
                         StackTraceElement[] elems = cause.getStackTrace();
-                        if (cause instanceof RemoteResolutionException) {
+                        if (cause instanceof SPIResolutionException) {
                             out.writeInt(RESULT_CORRALLED);
-                            out.writeInt(((RemoteResolutionException) cause).id);
+                            out.writeInt(((SPIResolutionException) cause).id());
                         } else {
                             out.writeInt(RESULT_EXCEPTION);
                             out.writeUTF(cause.getClass().getName());
@@ -254,27 +256,14 @@
         if (value == null) {
             return "null";
         } else if (value instanceof String) {
-            return "\"" + expunge((String)value) + "\"";
+            return "\"" + (String)value + "\"";
         } else if (value instanceof Character) {
             return "'" + value + "'";
         } else {
-            return expunge(value.toString());
+            return value.toString();
         }
     }
 
-    /**
-     * Expunge internal info from string
-     * @param s string to process
-     * @return string the display, JShell package and wrapper class names removed
-     */
-    static String expunge(String s) {
-        StringBuilder sb = new StringBuilder();
-        for (String comp : PREFIX_PATTERN.split(s)) {
-            sb.append(comp);
-        }
-        return sb.toString();
-    }
-
     private static final class MultiplexingOutputStream extends OutputStream {
 
         private static final int PACKET_SIZE = 127;
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteCodes.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteCodes.java	Sat May 21 22:32:08 2016 -0700
@@ -25,8 +25,6 @@
 
 package jdk.internal.jshell.remote;
 
-import java.util.regex.Pattern;
-
 /**
  * Communication constants shared between the main process and the remote
  * execution process
@@ -46,13 +44,4 @@
     public static final int RESULT_EXCEPTION = 102;
     public static final int RESULT_CORRALLED = 103;
     public static final int RESULT_KILLED    = 104;
-
-    // String constants
-    public static final String REPL_PACKAGE = "REPL";
-    public static final String REPL_CLASS_PREFIX = "$JShell$";
-    public static final String DOIT_METHOD_NAME = "do_it$";
-    public static final Pattern PREFIX_PATTERN = Pattern.compile(
-            "(" + REPL_PACKAGE + "\\.)?" +
-            "(?<class>" + Pattern.quote(REPL_CLASS_PREFIX) +
-            "\\w+" + ")" + "[\\$\\.]?");
 }
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteResolutionException.java	Fri May 20 17:00:03 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 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;
-
-/**
- * The exception thrown on the remote side upon executing a
- * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
- * user method. This exception is not seen by the end user nor through the API.
- * @author Robert Field
- */
-@SuppressWarnings("serial")             // serialVersionUID intentionally omitted
-public class RemoteResolutionException extends RuntimeException {
-
-    final int id;
-
-    /**
-     * The throw of this exception is generated into the body of a
-     * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
-     * method.
-     * @param id An internal identifier of the specific method
-     */
-    public RemoteResolutionException(int id) {
-        super("RemoteResolutionException");
-        this.id = id;
-    }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Sat May 21 22:32:08 2016 -0700
@@ -101,8 +101,13 @@
 import jdk.internal.jshell.tool.Feedback.FormatUnresolved;
 import jdk.internal.jshell.tool.Feedback.FormatWhen;
 import static java.util.stream.Collectors.toList;
+import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND;
 import static java.util.stream.Collectors.toMap;
-import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
 
 /**
  * Command line REPL tool for Java using the JShell API.
@@ -1344,7 +1349,7 @@
     boolean cmdDebug(String arg) {
         if (arg.isEmpty()) {
             debug = !debug;
-            InternalDebugControl.setDebugFlags(state, debug ? InternalDebugControl.DBG_GEN : 0);
+            InternalDebugControl.setDebugFlags(state, debug ? DBG_GEN : 0);
             fluff("Debugging %s", debug ? "on" : "off");
         } else {
             int flags = 0;
@@ -1360,23 +1365,23 @@
                         fluff("REPL tool debugging on");
                         break;
                     case 'g':
-                        flags |= InternalDebugControl.DBG_GEN;
+                        flags |= DBG_GEN;
                         fluff("General debugging on");
                         break;
                     case 'f':
-                        flags |= InternalDebugControl.DBG_FMGR;
+                        flags |= DBG_FMGR;
                         fluff("File manager debugging on");
                         break;
                     case 'c':
-                        flags |= InternalDebugControl.DBG_COMPA;
+                        flags |= DBG_COMPA;
                         fluff("Completion analysis debugging on");
                         break;
                     case 'd':
-                        flags |= InternalDebugControl.DBG_DEP;
+                        flags |= DBG_DEP;
                         fluff("Dependency debugging on");
                         break;
                     case 'e':
-                        flags |= InternalDebugControl.DBG_EVNT;
+                        flags |= DBG_EVNT;
                         fluff("Event debugging on");
                         break;
                     default:
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/ClassTracker.java	Fri May 20 17:00:03 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-/*
- * Copyright (c) 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.jshell;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Objects;
-import com.sun.jdi.ReferenceType;
-
-/**
- * Tracks the state of a class through compilation and loading in the remote
- * environment.
- *
- * @author Robert Field
- */
-class ClassTracker {
-
-    private final JShell state;
-    private final HashMap<String, ClassInfo> map;
-
-    ClassTracker(JShell state) {
-        this.state = state;
-        this.map = new HashMap<>();
-    }
-
-    class ClassInfo {
-
-        private final String className;
-        private byte[] bytes;
-        private byte[] loadedBytes;
-        private ReferenceType rt;
-
-        private ClassInfo(String className) {
-            this.className = className;
-        }
-
-        String getClassName() {
-            return className;
-        }
-
-        byte[] getBytes() {
-            return bytes;
-        }
-
-        void setBytes(byte[] bytes) {
-            this.bytes = bytes;
-        }
-
-        void setLoaded() {
-            loadedBytes = bytes;
-        }
-
-        boolean isLoaded() {
-            return Arrays.equals(loadedBytes, bytes);
-        }
-
-        ReferenceType getReferenceTypeOrNull() {
-            if (rt == null) {
-                rt = state.executionControl().nameToRef(className);
-            }
-            return rt;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            return o instanceof ClassInfo &&
-                    ((ClassInfo) o).className.equals(className);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(this.className);
-        }
-    }
-
-    ClassInfo classInfo(String className, byte[] bytes) {
-        ClassInfo ci = map.computeIfAbsent(className, k -> new ClassInfo(k));
-        ci.setBytes(bytes);
-        return ci;
-    }
-
-    ClassInfo get(String className) {
-        return map.get(className);
-    }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Corraller.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Corraller.java	Sat May 21 22:32:08 2016 -0700
@@ -49,6 +49,8 @@
 import static com.sun.tools.javac.code.Flags.INTERFACE;
 import static com.sun.tools.javac.code.Flags.ENUM;
 import static com.sun.tools.javac.code.Flags.PUBLIC;
+import com.sun.tools.javac.util.Name;
+import jdk.jshell.spi.SPIResolutionException;
 
 /**
  * Produce a corralled version of the Wrap for a snippet.
@@ -129,16 +131,22 @@
         super.visitClassDef(tree);
     }
 
+    // Build a compiler tree for an exception throwing block, e.g.:
+    // {
+    //     throw new jdk.jshell.spi.SPIResolutionException(9);
+    // }
     private JCBlock resolutionExceptionBlock() {
         if (resolutionExceptionBlock == null) {
-            JCExpression expClass
-                    = make.Select(make.Select(make.Select(make.Select(
-                            make.Ident(names.fromString("jdk")),
-                            names.fromString("internal")),
-                            names.fromString("jshell")),
-                            names.fromString("remote")),
-                            names.fromString("RemoteResolutionException")
-                    );
+            JCExpression expClass = null;
+            // Split the exception class name at dots
+            for (String id : SPIResolutionException.class.getName().split("\\.")) {
+                Name nm = names.fromString(id);
+                if (expClass == null) {
+                    expClass = make.Ident(nm);
+                } else {
+                    expClass = make.Select(expClass, nm);
+                }
+            }
             JCNewClass exp = make.NewClass(null,
                     null, expClass, List.of(make.Literal(keyIndex)), null);
             resolutionExceptionBlock = make.Block(0L, List.<JCStatement>of(
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java	Sat May 21 22:32:08 2016 -0700
@@ -49,7 +49,6 @@
 import java.io.Writer;
 import java.util.LinkedHashSet;
 import java.util.Set;
-import jdk.jshell.ClassTracker.ClassInfo;
 import jdk.jshell.Key.ErroneousKey;
 import jdk.jshell.Key.MethodKey;
 import jdk.jshell.Key.TypeDeclKey;
@@ -64,9 +63,9 @@
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
 import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
-import static jdk.jshell.Util.*;
-import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
-import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
+import static jdk.jshell.Util.DOIT_METHOD_NAME;
+import static jdk.jshell.Util.PREFIX_PATTERN;
+import static jdk.jshell.Util.expunge;
 import static jdk.jshell.Snippet.SubKind.SINGLE_TYPE_IMPORT_SUBKIND;
 import static jdk.jshell.Snippet.SubKind.SINGLE_STATIC_IMPORT_SUBKIND;
 import static jdk.jshell.Snippet.SubKind.TYPE_IMPORT_ON_DEMAND_SUBKIND;
@@ -456,7 +455,7 @@
         if (si.status().isDefined) {
             if (si.isExecutable()) {
                 try {
-                value = state.executionControl().commandInvoke(si.classFullName());
+                value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
                     value = si.subKind().hasValue()
                             ? expunge(value)
                             : "";
@@ -578,7 +577,7 @@
 
                     // load all new classes
                     load(legit.stream()
-                            .flatMap(u -> u.classesToLoad(ct.classInfoList(u.snippet().outerWrap())))
+                            .flatMap(u -> u.classesToLoad(ct.classList(u.snippet().outerWrap())))
                             .collect(toSet()));
                     // attempt to redefine the remaining classes
                     List<Unit> toReplace = legit.stream()
@@ -613,9 +612,13 @@
         }
     }
 
-    private void load(Set<ClassInfo> cil) {
-        if (!cil.isEmpty()) {
-            state.executionControl().commandLoad(cil);
+    /**
+     * If there are classes to load, loads by calling the execution engine.
+     * @param classnames names of the classes to load.
+     */
+    private void load(Collection<String> classnames) {
+        if (!classnames.isEmpty()) {
+            state.executionControl().load(classnames);
         }
     }
 
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/ExecutionControl.java	Fri May 20 17:00:03 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,434 +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.jshell;
-
-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 com.sun.jdi.*;
-import java.io.EOFException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import jdk.jshell.ClassTracker.ClassInfo;
-import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
-
-/**
- * Controls the remote execution environment.
- *
- * @author Robert Field
- */
-class ExecutionControl {
-
-    private final JDIEnv env;
-    private final SnippetMaps maps;
-    private JDIEventHandler handler;
-    private Socket socket;
-    private ObjectInputStream in;
-    private ObjectOutputStream out;
-    private final JShell proc;
-    private final String remoteVMOptions;
-
-    ExecutionControl(JDIEnv env, SnippetMaps maps, JShell proc, List<String> extraRemoteVMOptions) {
-        this.env = env;
-        this.maps = maps;
-        this.proc = proc;
-        StringBuilder sb = new StringBuilder();
-        extraRemoteVMOptions.stream()
-                .forEach(s -> {
-                    sb.append(" ");
-                    sb.append(s);
-                });
-        this.remoteVMOptions = sb.toString();
-    }
-
-    void launch() throws IOException {
-        try (ServerSocket listener = new ServerSocket(0)) {
-            // timeout after 60 seconds
-            listener.setSoTimeout(60000);
-            int port = listener.getLocalPort();
-            jdiGo(port);
-            socket = listener.accept();
-            // out before in -- match remote creation so we don't hang
-            out = new ObjectOutputStream(socket.getOutputStream());
-            PipeInputStream commandIn = new PipeInputStream();
-            new DemultiplexInput(socket.getInputStream(), commandIn, proc.out, proc.err).start();
-            in = new ObjectInputStream(commandIn);
-        }
-    }
-
-    void commandExit() {
-        try {
-            if (out != null) {
-                out.writeInt(CMD_EXIT);
-                out.flush();
-            }
-            JDIConnection c = env.connection();
-            if (c != null) {
-                c.disposeVM();
-            }
-        } catch (IOException ex) {
-            proc.debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
-        }
-    }
-
-
-    boolean commandLoad(Collection<ClassInfo> cil) {
-        try {
-            out.writeInt(CMD_LOAD);
-            out.writeInt(cil.size());
-            for (ClassInfo ci : cil) {
-                out.writeUTF(ci.getClassName());
-                out.writeObject(ci.getBytes());
-            }
-            out.flush();
-            return readAndReportResult();
-        } catch (IOException ex) {
-            proc.debug(DBG_GEN, "IOException on remote load operation: %s\n", ex);
-            return false;
-        }
-    }
-
-    String commandInvoke(String classname) throws JShellException {
-        try {
-            synchronized (STOP_LOCK) {
-                userCodeRunning = true;
-            }
-            out.writeInt(CMD_INVOKE);
-            out.writeUTF(classname);
-            out.flush();
-            if (readAndReportExecutionResult()) {
-                String result = in.readUTF();
-                return result;
-            }
-        } catch (IOException | RuntimeException ex) {
-            if (!env.connection().isRunning()) {
-                env.shutdown();
-            } else {
-                proc.debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
-                return "Execution failure: " + ex.getMessage();
-            }
-        } finally {
-            synchronized (STOP_LOCK) {
-                userCodeRunning = false;
-            }
-        }
-        return "";
-    }
-
-    String commandVarValue(String classname, String varname) {
-        try {
-            out.writeInt(CMD_VARVALUE);
-            out.writeUTF(classname);
-            out.writeUTF(varname);
-            out.flush();
-            if (readAndReportResult()) {
-                String result = in.readUTF();
-                return result;
-            }
-        } catch (EOFException ex) {
-            env.shutdown();
-        } catch (IOException ex) {
-            proc.debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
-            return "Execution failure: " + ex.getMessage();
-        }
-        return "";
-    }
-
-    boolean commandAddToClasspath(String cp) {
-        try {
-            out.writeInt(CMD_CLASSPATH);
-            out.writeUTF(cp);
-            out.flush();
-            return readAndReportResult();
-        } catch (IOException ex) {
-            throw new InternalError("Classpath addition failed: " + cp, ex);
-        }
-    }
-
-    boolean commandRedefine(Map<ReferenceType, byte[]> mp) {
-        try {
-            env.vm().redefineClasses(mp);
-            return true;
-        } catch (UnsupportedOperationException ex) {
-            return false;
-        } catch (Exception ex) {
-            proc.debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex);
-            return false;
-        }
-    }
-
-    ReferenceType nameToRef(String name) {
-        List<ReferenceType> rtl = env.vm().classesByName(name);
-        if (rtl.size() != 1) {
-            return null;
-        }
-        return rtl.get(0);
-    }
-
-    private boolean readAndReportResult() throws IOException {
-        int ok = in.readInt();
-        switch (ok) {
-            case RESULT_SUCCESS:
-                return true;
-            case RESULT_FAIL: {
-                String ex = in.readUTF();
-                proc.debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
-                return false;
-            }
-            default: {
-                proc.debug(DBG_GEN, "Bad remote result code: %s\n", ok);
-                return false;
-            }
-        }
-    }
-
-    private boolean readAndReportExecutionResult() throws IOException, JShellException {
-        int ok = in.readInt();
-        switch (ok) {
-            case RESULT_SUCCESS:
-                return true;
-            case RESULT_FAIL: {
-                String ex = in.readUTF();
-                proc.debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
-                return false;
-            }
-            case RESULT_EXCEPTION: {
-                String exceptionClassName = in.readUTF();
-                String message = in.readUTF();
-                StackTraceElement[] elems = readStackTrace();
-                EvalException ee = new EvalException(message, exceptionClassName, elems);
-                throw ee;
-            }
-            case RESULT_CORRALLED: {
-                int id = in.readInt();
-                StackTraceElement[] elems = readStackTrace();
-                Snippet si = maps.getSnippetDeadOrAlive(id);
-                throw new UnresolvedReferenceException((DeclarationSnippet) si, elems);
-            }
-            case RESULT_KILLED: {
-                proc.out.println("Killed.");
-                return false;
-            }
-            default: {
-                proc.debug(DBG_GEN, "Bad remote result code: %s\n", ok);
-                return false;
-            }
-        }
-    }
-
-    private StackTraceElement[] readStackTrace() throws IOException {
-        int elemCount = in.readInt();
-        StackTraceElement[] elems = new StackTraceElement[elemCount];
-        for (int i = 0; i < elemCount; ++i) {
-            String className = in.readUTF();
-            String methodName = in.readUTF();
-            String fileName = in.readUTF();
-            int line = in.readInt();
-            elems[i] = new StackTraceElement(className, methodName, fileName, line);
-        }
-        return elems;
-    }
-
-    private void jdiGo(int port) {
-        //MessageOutput.textResources = ResourceBundle.getBundle("impl.TTYResources",
-        //        Locale.getDefault());
-
-        String connectorName = "com.sun.jdi.CommandLineLaunch";
-        Map<String, String> argumentName2Value = new HashMap<>();
-        argumentName2Value.put("main", "jdk.internal.jshell.remote.RemoteAgent " + port);
-        argumentName2Value.put("options", remoteVMOptions);
-
-        boolean launchImmediately = true;
-        int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
-
-        env.init(connectorName, argumentName2Value, launchImmediately, traceFlags);
-
-        if (env.connection().isOpen() && env.vm().canBeModified()) {
-            /*
-             * Connection opened on startup. Start event handler
-             * immediately, telling it (through arg 2) to stop on the
-             * VM start event.
-             */
-            handler = new JDIEventHandler(env);
-        }
-    }
-
-    private final Object STOP_LOCK = new Object();
-    private boolean userCodeRunning = false;
-
-    void commandStop() {
-        synchronized (STOP_LOCK) {
-            if (!userCodeRunning)
-                return ;
-
-            VirtualMachine vm = handler.env.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();
-                                proc.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) {
-                proc.debug(DBG_GEN, "Exception on remote stop: %s\n", ex);
-            } finally {
-                vm.resume();
-            }
-        }
-    }
-
-    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) {
-                proc.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/jshell/JDIConnection.java	Fri May 20 17:00:03 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,347 +0,0 @@
-/*
- * Copyright (c) 1998, 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.
- */
-
-/*
- * 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.jshell;
-
-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 VirtualMachine vm;
-    private Process process = null;
-    private int outputCompleteCount = 0;
-
-    private final JShell proc;
-    private final JDIEnv env;
-    private final Connector connector;
-    private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
-    private final int traceFlags;
-
-    synchronized void notifyOutputComplete() {
-        outputCompleteCount++;
-        notifyAll();
-    }
-
-    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;
-    }
-
-    JDIConnection(JDIEnv env, String connectorName, Map<String, String> argumentName2Value, int traceFlags, JShell proc) {
-        this.env = env;
-        this.proc = proc;
-        this.connector = findConnector(connectorName);
-
-        if (connector == null) {
-            throw new IllegalArgumentException("No connector named: " + connectorName);
-        }
-
-        connectorArgs = mergeConnectorArgs(connector, argumentName2Value);
-        this.traceFlags = traceFlags;
-    }
-
-    synchronized VirtualMachine open() {
-        if (connector instanceof LaunchingConnector) {
-            vm = launchTarget();
-        } else if (connector instanceof AttachingConnector) {
-            vm = attachTarget();
-        } else if (connector instanceof ListeningConnector) {
-            vm = listenTarget();
-        } else {
-            throw new InternalError("Invalid connect type");
-        }
-        vm.setDebugTraceMode(traceFlags);
-        // Uncomment here and below to enable event requests
-        // installEventRequests(vm);
-
-        return vm;
-    }
-
-    synchronized boolean setConnectorArg(String name, String value) {
-        /*
-         * Too late if the connection already made
-         */
-        if (vm != null) {
-            return false;
-        }
-
-        Connector.Argument argument = connectorArgs.get(name);
-        if (argument == null) {
-            return false;
-        }
-        argument.setValue(value);
-        return true;
-    }
-
-    String connectorArg(String name) {
-        Connector.Argument argument = connectorArgs.get(name);
-        if (argument == null) {
-            return "";
-        }
-        return argument.value();
-    }
-
-    public synchronized VirtualMachine vm() {
-        if (vm == null) {
-            throw new JDINotConnectedException();
-        } else {
-            return vm;
-        }
-    }
-
-    synchronized boolean isOpen() {
-        return (vm != null);
-    }
-
-    boolean isLaunch() {
-        return (connector instanceof LaunchingConnector);
-    }
-
-    synchronized boolean isRunning() {
-        return process != null && process.isAlive();
-    }
-
-    public 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
-        } finally {
-            if (process != null) {
-                process.destroy();
-                process = null;
-            }
-            waitOutputComplete();
-        }
-    }
-
-/*** Preserved for possible future support of event requests
-
-    private void installEventRequests(VirtualMachine vm) {
-        if (vm.canBeModified()){
-            setEventRequests(vm);
-            resolveEventRequests();
-        }
-    }
-
-    private void setEventRequests(VirtualMachine vm) {
-        EventRequestManager erm = vm.eventRequestManager();
-
-        // Normally, we want all uncaught exceptions.  We request them
-        // via the same mechanism as Commands.commandCatchException()
-        // so the user can ignore them later if they are not
-        // interested.
-        // FIXME: this works but generates spurious messages on stdout
-        //        during startup:
-        //          Set uncaught java.lang.Throwable
-        //          Set deferred uncaught java.lang.Throwable
-        Commands evaluator = new Commands();
-        evaluator.commandCatchException
-            (new StringTokenizer("uncaught java.lang.Throwable"));
-
-        ThreadStartRequest tsr = erm.createThreadStartRequest();
-        tsr.enable();
-        ThreadDeathRequest tdr = erm.createThreadDeathRequest();
-        tdr.enable();
-    }
-
-    private void resolveEventRequests() {
-        Env.specList.resolveAll();
-    }
-***/
-
-    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) {
-                pStream.print((char) i);
-            }
-        } catch (IOException ex) {
-            String s = ex.getMessage();
-            if (!s.startsWith("Bad file number")) {
-                throw ex;
-            }
-            // else 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) {
-                    proc.debug(ex, "Failed reading output");
-                    env.shutdown();
-                } 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) {
-                    proc.debug(ex, "Failed reading output");
-                    env.shutdown();
-                }
-            }
-        };
-        thr.setPriority(Thread.MAX_PRIORITY-1);
-        thr.start();
-    }
-
-    /* launch child target vm */
-    private VirtualMachine launchTarget() {
-        LaunchingConnector launcher = (LaunchingConnector)connector;
-        try {
-            VirtualMachine new_vm = launcher.launch(connectorArgs);
-            process = new_vm.process();
-            displayRemoteOutput(process.getErrorStream(), proc.err);
-            displayRemoteOutput(process.getInputStream(), proc.out);
-            readRemoteInput(process.getOutputStream(), proc.in);
-            return new_vm;
-        } catch (Exception ex) {
-            reportLaunchFail(ex, "launch");
-        }
-        return null;
-    }
-
-    /* JShell currently uses only launch, preserved for futures: */
-    /* attach to running target vm */
-    private VirtualMachine attachTarget() {
-        AttachingConnector attacher = (AttachingConnector)connector;
-        try {
-            return attacher.attach(connectorArgs);
-        } catch (Exception ex) {
-            reportLaunchFail(ex, "attach");
-        }
-        return null;
-    }
-
-    /* JShell currently uses only launch, preserved for futures: */
-    /* listen for connection from target vm */
-    private VirtualMachine listenTarget() {
-        ListeningConnector listener = (ListeningConnector)connector;
-        try {
-            String retAddress = listener.startListening(connectorArgs);
-            proc.debug(DBG_GEN, "Listening at address: " + retAddress);
-            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);
-    }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIEnv.java	Fri May 20 17:00:03 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-/*
- * Copyright (c) 1998, 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.jshell;
-
-import java.util.Map;
-
-import com.sun.jdi.*;
-import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
-
-/**
- * Representation of a Java Debug Interface environment
- * Select methods extracted from jdb Env; shutdown() adapted to JShell shutdown.
- */
-class JDIEnv {
-
-    private JDIConnection connection;
-    private final JShell state;
-
-    JDIEnv(JShell state) {
-        this.state = state;
-    }
-
-    void init(String connectorName, Map<String, String> argumentName2Value, boolean openNow, int flags) {
-        connection = new JDIConnection(this, connectorName, argumentName2Value, flags, state);
-        if (!connection.isLaunch() || openNow) {
-            connection.open();
-        }
-    }
-
-    JDIConnection connection() {
-        return connection;
-    }
-
-    VirtualMachine vm() {
-        return connection.vm();
-    }
-
-    void shutdown() {
-        if (connection != null) {
-            try {
-                connection.disposeVM();
-            } catch (VMDisconnectedException e) {
-                // Shutting down after the VM has gone away. This is
-                // not an error, and we just ignore it.
-            } catch (Throwable e) {
-                state.debug(DBG_GEN, null, "disposeVM threw: " + e);
-            }
-        }
-        if (state != null) { // If state has been set-up
-            try {
-                state.closeDown();
-            } catch (Throwable e) {
-                state.debug(DBG_GEN, null, "state().closeDown() threw: " + e);
-            }
-        }
-    }
-}
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIEventHandler.java	Fri May 20 17:00:03 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +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.jshell;
-
-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 {
-
-    Thread thread;
-    volatile boolean connected = true;
-    boolean completed = false;
-    String shutdownMessageKey;
-    final JDIEnv env;
-
-    JDIEventHandler(JDIEnv env) {
-        this.env = env;
-        this.thread = new Thread(this, "event-handler");
-        this.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 = env.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;
-            shutdownMessageKey = "The application exited";
-        } else if (event instanceof VMDisconnectEvent) {
-            connected = false;
-            if (!vmDied) {
-                shutdownMessageKey = "The application has been disconnected";
-            }
-        } else {
-            throw new InternalError("Unexpected event type: " +
-                    event.getClass());
-        }
-        env.shutdown();
-    }
-
-    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 = env.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/jshell/JDINotConnectedException.java	Fri May 20 17:00:03 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.jshell;
-
-/**
- * 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/jshell/JShell.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java	Sat May 21 22:32:08 2016 -0700
@@ -25,6 +25,7 @@
 
 package jdk.jshell;
 
+import jdk.jshell.spi.ExecutionControl;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.io.PrintStream;
@@ -47,6 +48,8 @@
 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
@@ -71,7 +74,6 @@
  * <p>
  * This class is not thread safe, except as noted, all access should be through
  * a single thread.
- * @see jdk.jshell
  * @author Robert Field
  */
 public class JShell implements AutoCloseable {
@@ -86,16 +88,17 @@
     final Supplier<String> tempVariableNameGenerator;
     final BiFunction<Snippet, Integer, String> idGenerator;
     final List<String> extraRemoteVMOptions;
+    final ExecutionControl executionControl;
 
     private int nextKeyIndex = 1;
 
     final Eval eval;
-    final ClassTracker classTracker;
+    private final Map<String, byte[]> classnameToBytes = new HashMap<>();
     private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>();
     private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>();
     private boolean closed = false;
 
-    private ExecutionControl executionControl = null;
+    private boolean executionControlLaunched = false;
     private SourceCodeAnalysisImpl sourceCodeAnalysis = null;
 
     private static final String L10N_RB_NAME    = "jdk.jshell.resources.l10n";
@@ -108,13 +111,15 @@
         this.tempVariableNameGenerator = b.tempVariableNameGenerator;
         this.idGenerator = b.idGenerator;
         this.extraRemoteVMOptions = b.extraRemoteVMOptions;
+        this.executionControl = b.executionControl==null
+                ? new JDIExecutionControl()
+                : b.executionControl;
 
         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(this);
     }
 
     /**
@@ -143,22 +148,23 @@
         Supplier<String> tempVariableNameGenerator = null;
         BiFunction<Snippet, Integer, String> idGenerator = null;
         List<String> extraRemoteVMOptions = new ArrayList<>();
+        ExecutionControl executionControl;
 
         Builder() { }
 
         /**
-         * Input for the running evaluation (it's <code>System.in</code>). Note:
-         * applications that use <code>System.in</code> for snippet or other
-         * user input cannot use <code>System.in</code> as the input stream for
+         * Sets the input for the running evaluation (it's {@code System.in}). Note:
+         * applications that use {@code System.in} for snippet or other
+         * user input cannot use {@code System.in} as the input stream for
          * the remote process.
          * <p>
          * The default, if this is not set, is to provide an empty input stream
-         * -- <code>new ByteArrayInputStream(new byte[0])</code>.
+         * -- {@code new ByteArrayInputStream(new byte[0])}.
          *
-         * @param in the <code>InputStream</code> to be channelled to
-         * <code>System.in</code> in the remote execution process.
-         * @return the <code>Builder</code> instance (for use in chained
-         * initialization).
+         * @param in the {@code InputStream} to be channelled to
+         * {@code System.in} in the remote execution process
+         * @return the {@code Builder} instance (for use in chained
+         * initialization)
          */
         public Builder in(InputStream in) {
             this.in = in;
@@ -166,16 +172,16 @@
         }
 
         /**
-         * Output for the running evaluation (it's <code>System.out</code>).
+         * Sets the output for the running evaluation (it's {@code System.out}).
          * The controlling process and
-         * the remote process can share <code>System.out</code>.
+         * the remote process can share {@code System.out}.
          * <p>
-         * The default, if this is not set, is <code>System.out</code>.
+         * The default, if this is not set, is {@code System.out}.
          *
-         * @param out the <code>PrintStream</code> to be channelled to
-         * <code>System.out</code> in the remote execution process.
-         * @return the <code>Builder</code> instance (for use in chained
-         * initialization).
+         * @param out the {@code PrintStream} to be channelled to
+         * {@code System.out} in the remote execution process
+         * @return the {@code Builder} instance (for use in chained
+         * initialization)
          */
         public Builder out(PrintStream out) {
             this.out = out;
@@ -183,16 +189,16 @@
         }
 
         /**
-         * Error output for the running evaluation (it's
-         * <code>System.err</code>). The controlling process and the remote
-         * process can share <code>System.err</code>.
+         * Sets the error output for the running evaluation (it's
+         * {@code System.err}). The controlling process and the remote
+         * process can share {@code System.err}.
          * <p>
-         * The default, if this is not set, is <code>System.err</code>.
+         * The default, if this is not set, is {@code System.err}.
          *
-         * @param err the <code>PrintStream</code> to be channelled to
-         * <code>System.err</code> in the remote execution process.
-         * @return the <code>Builder</code> instance (for use in chained
-         * initialization).
+         * @param err the {@code PrintStream} to be channelled to
+         * {@code System.err} in the remote execution process
+         * @return the {@code Builder} instance (for use in chained
+         * initialization)
          */
         public Builder err(PrintStream err) {
             this.err = err;
@@ -200,7 +206,7 @@
         }
 
         /**
-         * Set a generator of temp variable names for
+         * Sets a generator of temp variable names for
          * {@link jdk.jshell.VarSnippet} of
          * {@link jdk.jshell.Snippet.SubKind#TEMP_VAR_EXPRESSION_SUBKIND}.
          * <p>
@@ -221,9 +227,9 @@
          * prefixing dollar sign ("$").
          *
          * @param generator the <code>Supplier</code> to generate the temporary
-         * variable name string or <code>null</code>.
-         * @return the <code>Builder</code> instance (for use in chained
-         * initialization).
+         * variable name string or <code>null</code>
+         * @return the {@code Builder} instance (for use in chained
+         * initialization)
          */
         public Builder tempVariableNameGenerator(Supplier<String> generator) {
             this.tempVariableNameGenerator = generator;
@@ -231,7 +237,7 @@
         }
 
         /**
-         * Set the generator of identifying names for Snippets.
+         * Sets the generator of identifying names for Snippets.
          * <p>
          * Do not use this method unless you have explicit need for it.
          * <p>
@@ -258,9 +264,9 @@
          * is null) is to generate the id as the integer converted to a string.
          *
          * @param generator the <code>BiFunction</code> to generate the id
-         * string or <code>null</code>.
-         * @return the <code>Builder</code> instance (for use in chained
-         * initialization).
+         * string or <code>null</code>
+         * @return the {@code Builder} instance (for use in chained
+         * initialization)
          */
         public Builder idGenerator(BiFunction<Snippet, Integer, String> generator) {
             this.idGenerator = generator;
@@ -268,11 +274,11 @@
         }
 
         /**
-         * Set additional VM options for launching the VM.
+         * Sets additional VM options for launching the VM.
          *
-         * @param options The options for the remote VM.
-         * @return the <code>Builder</code> instance (for use in chained
-         * initialization).
+         * @param options The options for the remote VM
+         * @return the {@code Builder} instance (for use in chained
+         * initialization)
          */
         public Builder remoteVMOptions(String... options) {
             this.extraRemoteVMOptions.addAll(Arrays.asList(options));
@@ -280,11 +286,24 @@
         }
 
         /**
-         * Build a JShell state engine. This is the entry-point to all JShell
+         * Sets the custom engine for execution. Snippet execution will be
+         * provided by the specified {@link ExecutionControl} instance.
+         *
+         * @param execEngine the execution engine
+         * @return the {@code Builder} instance (for use in chained
+         * initialization)
+         */
+        public Builder executionEngine(ExecutionControl execEngine) {
+            this.executionControl = execEngine;
+            return this;
+        }
+
+        /**
+         * Builds a JShell state engine. This is the entry-point to all JShell
          * functionality. This creates a remote process for execution. It is
          * thus important to close the returned instance.
          *
-         * @return the state engine.
+         * @return the state engine
          */
         public JShell build() {
             return new JShell(this);
@@ -406,7 +425,7 @@
      */
     public void addToClasspath(String path) {
         taskFactory.addToClasspath(path);  // Compiler
-        executionControl().commandAddToClasspath(path);       // Runtime
+        executionControl().addToClasspath(path);       // Runtime
         if (sourceCodeAnalysis != null) {
             sourceCodeAnalysis.classpathChanged();
         }
@@ -427,7 +446,7 @@
      */
     public void stop() {
         if (executionControl != null)
-            executionControl.commandStop();
+            executionControl.stop();
     }
 
     /**
@@ -438,7 +457,7 @@
     public void close() {
         if (!closed) {
             closeDown();
-            executionControl().commandExit();
+            executionControl().close();
         }
     }
 
@@ -580,7 +599,7 @@
             throw new IllegalArgumentException(
                     messageFormat("jshell.exc.var.not.valid",  snippet, snippet.status()));
         }
-        String value = executionControl().commandVarValue(snippet.classFullName(), snippet.name());
+        String value = executionControl().varValue(snippet.classFullName(), snippet.name());
         return expunge(value);
     }
 
@@ -633,35 +652,86 @@
         }
     }
 
+    /**
+     * Provide the environment for a execution engine.
+     */
+    class ExecutionEnvImpl implements ExecutionEnv {
+
+        @Override
+        public InputStream userIn() {
+            return in;
+        }
+
+        @Override
+        public PrintStream userOut() {
+            return out;
+        }
+
+        @Override
+        public PrintStream userErr() {
+            return err;
+        }
+
+        @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 (executionControl == null) {
-            this.executionControl = new ExecutionControl(new JDIEnv(this), maps, this, extraRemoteVMOptions);
+        if (!executionControlLaunched) {
             try {
-                executionControl.launch();
+                executionControlLaunched = true;
+                executionControl.start(new ExecutionEnvImpl());
             } catch (Throwable ex) {
-                throw new InternalError("Launching JDI execution engine threw: " + ex.getMessage(), ex);
+                throw new InternalError("Launching execution engine threw: " + ex.getMessage(), ex);
             }
         }
         return executionControl;
     }
 
+    void setClassnameToBytes(String classname, byte[] bytes) {
+        classnameToBytes.put(classname, bytes);
+    }
+
     void debug(int flags, String format, Object... args) {
-        if (InternalDebugControl.debugEnabled(this, flags)) {
-            err.printf(format, args);
-        }
+        InternalDebugControl.debug(this, err, flags, format, args);
     }
 
     void debug(Exception ex, String where) {
-        if (InternalDebugControl.debugEnabled(this, 0xFFFFFFFF)) {
-            err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
-            ex.printStackTrace(err);
-        }
+        InternalDebugControl.debug(this, err, ex, where);
     }
 
     /**
      * Generate the next key index, indicating a unique snippet signature.
+     *
      * @return the next key index
      */
     int nextKeyIndex() {
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrap.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrap.java	Sat May 21 22:32:08 2016 -0700
@@ -28,9 +28,11 @@
 import java.util.Locale;
 import javax.tools.Diagnostic;
 import javax.tools.JavaFileObject;
-import jdk.internal.jshell.remote.RemoteCodes;
-import static jdk.jshell.Util.*;
-import static jdk.internal.jshell.remote.RemoteCodes.REPL_PACKAGE;
+import static jdk.jshell.Util.PARSED_LOCALE;
+import static jdk.jshell.Util.REPL_CLASS_PREFIX;
+import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
+import static jdk.jshell.Util.REPL_PACKAGE;
+import static jdk.jshell.Util.expunge;
 
 /**
  *
@@ -163,7 +165,7 @@
             }
             for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
                 if (line.trim().startsWith("location:")) {
-                    if (!line.contains(RemoteCodes.REPL_CLASS_PREFIX)) {
+                    if (!line.contains(REPL_CLASS_PREFIX)) {
                         // Resolution error must occur within a REPL class or it is not resolvable
                         return false;
                     }
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrapMap.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrapMap.java	Sat May 21 22:32:08 2016 -0700
@@ -35,8 +35,8 @@
 import java.util.regex.Matcher;
 import java.util.stream.Collectors;
 import jdk.jshell.Wrap.CompoundWrap;
-import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
-import static jdk.internal.jshell.remote.RemoteCodes.REPL_CLASS_PREFIX;
+import static jdk.jshell.Util.PREFIX_PATTERN;
+import static jdk.jshell.Util.REPL_CLASS_PREFIX;
 import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
 import static jdk.jshell.Util.asLetters;
 
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java	Sat May 21 22:32:08 2016 -0700
@@ -38,9 +38,9 @@
 import java.util.stream.Stream;
 
 import static java.util.stream.Collectors.toList;
-import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
+import static jdk.jshell.Util.PREFIX_PATTERN;
+import static jdk.jshell.Util.REPL_PACKAGE;
 import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP;
-import static jdk.internal.jshell.remote.RemoteCodes.REPL_PACKAGE;
 
 /**
  * Maintain relationships between the significant entities: Snippets,
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java	Sat May 21 22:32:08 2016 -0700
@@ -104,7 +104,6 @@
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import static java.util.stream.Collectors.collectingAndThen;
-import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toCollection;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
@@ -130,6 +129,7 @@
 import javax.tools.ToolProvider;
 
 import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
+import static java.util.stream.Collectors.joining;
 
 /**
  * The concrete implementation of SourceCodeAnalysis.
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java	Sat May 21 22:32:08 2016 -0700
@@ -60,7 +60,6 @@
 import javax.lang.model.util.Elements;
 import javax.tools.FileObject;
 import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
-import jdk.jshell.ClassTracker.ClassInfo;
 import java.lang.Runtime.Version;
 
 /**
@@ -278,13 +277,19 @@
             return result;
         }
 
-
-        List<ClassInfo> classInfoList(OuterWrap w) {
+        // Returns the list of classes generated during this compile.
+        // Stores the mapping between class name and current compiled bytes.
+        List<String> classList(OuterWrap w) {
             List<OutputMemoryJavaFileObject> l = classObjs.get(w);
-            if (l == null) return Collections.emptyList();
-            return l.stream()
-                    .map(fo -> state.classTracker.classInfo(fo.getName(), fo.getBytes()))
-                    .collect(Collectors.toList());
+            if (l == null) {
+                return Collections.emptyList();
+            }
+            List<String> list = new ArrayList<>();
+            for (OutputMemoryJavaFileObject fo : l) {
+                state.setClassnameToBytes(fo.getName(), fo.getBytes());
+                list.add(fo.getName());
+            }
+            return list;
         }
 
         private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java	Sat May 21 22:32:08 2016 -0700
@@ -30,18 +30,14 @@
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.stream.Stream;
-import com.sun.jdi.ReferenceType;
 import jdk.jshell.Snippet.Kind;
 import jdk.jshell.Snippet.Status;
 import jdk.jshell.Snippet.SubKind;
 import jdk.jshell.TaskFactory.AnalyzeTask;
-import jdk.jshell.ClassTracker.ClassInfo;
 import jdk.jshell.TaskFactory.CompileTask;
 import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toMap;
 import static java.util.stream.Collectors.toSet;
 import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
 import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
@@ -79,7 +75,7 @@
     private SnippetEvent replaceOldEvent;
     private List<SnippetEvent> secondaryEvents;
     private boolean isAttemptingCorral;
-    private List<ClassInfo> toRedefine;
+    private List<String> toRedefine;
     private boolean dependenciesNeeded;
 
     Unit(JShell state, Snippet si, Snippet causalSnippet,
@@ -260,10 +256,6 @@
                 si, status);
     }
 
-    /**
-     * Must be called for each unit
-     * @return
-     */
     boolean isDefined() {
         return status.isDefined;
     }
@@ -273,21 +265,28 @@
      * Requires loading of returned list.
      * @return the list of classes to load
      */
-    Stream<ClassInfo> classesToLoad(List<ClassInfo> cil) {
+    Stream<String> classesToLoad(List<String> classnames) {
         toRedefine = new ArrayList<>();
-        List<ClassInfo> toLoad = new ArrayList<>();
+        List<String> toLoad = new ArrayList<>();
         if (status.isDefined && !isImport()) {
-            cil.stream().forEach(ci -> {
-                if (!ci.isLoaded()) {
-                    if (ci.getReferenceTypeOrNull() == null) {
-                        toLoad.add(ci);
-                        ci.setLoaded();
+            // 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;
-                    } else {
-                        toRedefine.add(ci);
-                    }
+                        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;
                 }
-            });
+            }
         }
         return toLoad.stream();
     }
@@ -298,19 +297,9 @@
      * @return true if all redefines succeeded (can be vacuously true)
      */
     boolean doRedefines() {
-         if (toRedefine.isEmpty()) {
-            return true;
-        }
-        Map<ReferenceType, byte[]> mp = toRedefine.stream()
-                .collect(toMap(ci -> ci.getReferenceTypeOrNull(), ci -> ci.getBytes()));
-        if (state.executionControl().commandRedefine(mp)) {
-            // success, mark as loaded
-            toRedefine.stream().forEach(ci -> ci.setLoaded());
-            return true;
-        } else {
-            // failed to redefine
-            return false;
-        }
+        return toRedefine.isEmpty()
+                ? true
+                : state.executionControl().redefine(toRedefine);
     }
 
     void markForReplacement() {
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Util.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Util.java	Sat May 21 22:32:08 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -29,20 +29,40 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.regex.Pattern;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 import javax.lang.model.element.Name;
-import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
-import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
-import static jdk.internal.jshell.remote.RemoteCodes.REPL_CLASS_PREFIX;
 
 /**
- * Assorted shared utilities.
- * @author Robert Field
+ * Assorted shared utilities and constants.
  */
 class Util {
 
-    static final String REPL_DOESNOTMATTER_CLASS_NAME = REPL_CLASS_PREFIX+"DOESNOTMATTER";
+    /**
+     * The package name of all wrapper classes.
+     */
+    static final String REPL_PACKAGE = "REPL";
+
+    /**
+     * The prefix for all wrapper class names.
+     */
+    static final String REPL_CLASS_PREFIX = "$JShell$";
+
+    /**
+     * The name of the invoke method.
+     */
+    static final String DOIT_METHOD_NAME = "do_it$";
+
+    /**
+     * A pattern matching the full or simple class name of a wrapper class.
+     */
+    static final Pattern PREFIX_PATTERN = Pattern.compile(
+            "(" + REPL_PACKAGE + "\\.)?"
+            + "(?<class>" + Pattern.quote(REPL_CLASS_PREFIX)
+            + "\\w+" + ")" + "[\\$\\.]?");
+
+    static final String REPL_DOESNOTMATTER_CLASS_NAME = REPL_CLASS_PREFIX + "DOESNOTMATTER";
 
     static final Locale PARSED_LOCALE = Locale.ROOT;
 
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java	Sat May 21 22:32:08 2016 -0700
@@ -27,7 +27,7 @@
 
 import java.util.Arrays;
 import static java.util.stream.Collectors.joining;
-import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
+import static jdk.jshell.Util.DOIT_METHOD_NAME;
 
 /**
  * Wrapping of source into Java methods, fields, etc.  All but outer layer
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionControl.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,154 @@
+/*
+ * 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.spi;
+
+import java.util.Collection;
+import jdk.jshell.JShellException;
+
+/**
+ * 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.
+ * <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.
+ * <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) }.
+ */
+public interface ExecutionControl {
+
+    /**
+     * Represents the current status of a class in the execution engine.
+     */
+    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,
+
+        /**
+         * Class is loaded and loaded/redefined bytes match those
+         * returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }.
+         */
+        CURRENT
+    };
+
+    /**
+     * Initializes the instance. No methods in this interface can be called
+     * before this.
+     *
+     * @param env the execution environment information provided by JShell
+     * @throws Exception if the instance is unable to initialize
+     */
+    void start(ExecutionEnv env) throws Exception;
+
+    /**
+     * Shuts down this execution engine. Implementation should free all
+     * resources held by this execution engine.
+     * <p>
+     * No calls to methods on this interface should be made after close.
+     */
+    void close();
+
+    /**
+     * Adds the path to the execution class path.
+     *
+     * @param path the path to add
+     * @return true if successful
+     */
+    boolean addToClasspath(String path);
+
+    /**
+     * 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
+     */
+    String invoke(String classname, String methodname) throws JShellException;
+
+    /**
+     * 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
+     */
+    boolean load(Collection<String> classes);
+
+    /**
+     * 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
+     */
+    boolean redefine(Collection<String> classes);
+
+    /**
+     * 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}
+     */
+    ClassStatus getClassStatus(String classname);
+
+    /**
+     * Interrupt a running invoke.
+     */
+    void stop();
+
+    /**
+     * 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
+     */
+    String varValue(String classname, String varname);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/ExecutionEnv.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,127 @@
+/*
+ * 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.spi;
+
+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) }.
+ * <p>
+ * This interface is designed to provide the access to core JShell functionality
+ * needed to implement ExecutionControl.
+ *
+ * @see ExecutionControl
+ */
+public interface ExecutionEnv {
+
+    /**
+     * Returns the user's input stream.
+     *
+     * @return the user's input stream
+     */
+    InputStream userIn();
+
+    /**
+     * Returns the user's output stream.
+     *
+     * @return the user's output stream
+     */
+    PrintStream userOut();
+
+    /**
+     * Returns the user's error stream.
+     *
+     * @return the user's error stream
+     */
+    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>
+     * Note: an execution engine need not launch a remote JVM.
+     *
+     * @return the additional options with which to launch the remote JVM
+     */
+    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();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/SPIResolutionException.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,69 @@
+/*
+ * 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.spi;
+
+/**
+ * The construction and throw of this exception is embedded in code generated by
+ * the JShell core implementation in such a way that, upon executing a
+ * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
+ * user method, this exception is thrown.
+ * <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 {
+
+    private final int id;
+
+    /**
+     * Constructs an SPI layer exception indicating that a
+     * {@code DeclarationSnippet} with unresolved references has been
+     * encountered. The throw of this exception is generated into the body of a
+     * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
+     * method.
+     *
+     * @param id An internal identifier of the specific method
+     */
+    public SPIResolutionException(int id) {
+        super("resolution exception");
+        this.id = id;
+    }
+
+    /**
+     * Retrieves the internal identifer of the unresolved identifer.
+     *
+     * @return the internal identifer
+     * @see ExecutionEnv#createUnresolvedReferenceException(int,
+     * java.lang.StackTraceElement[])
+     */
+    public int id() {
+        return id;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/spi/package-info.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides support for alternate implementations of the JShell execution
+ * engine.  The JShell core tracks and compiles Snippets then sends them
+ * (represented in a wrapper class) to the execution engine for loading,
+ * and in the case of executable Snippets, execution.  The JShell
+ * implementation includes a default execution engine (currently a remote
+ * process which is JDI controlled).  By implementing the
+ * {@link ExecutionControl} interface and installing it with
+ * {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }
+ * other execution engines can be used.
+ * <p>
+ * This is not a part of the JShell API.
+ */
+package jdk.jshell.spi;
--- a/langtools/src/jdk.jshell/share/classes/module-info.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/module-info.java	Sat May 21 22:32:08 2016 -0700
@@ -32,4 +32,5 @@
     requires jdk.jdi;
 
     exports jdk.jshell;
+    exports jdk.jshell.spi;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ExecutionControlTest.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,150 @@
+/*
+ * 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 8156101
+ * @summary Tests for ExecutionControl SPI
+ * @build KullaTesting LocalExecutionControl
+ * @run testng ExecutionControlTest
+ */
+
+
+import javax.tools.Diagnostic;
+
+import jdk.jshell.VarSnippet;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.VALID;
+import static jdk.jshell.Snippet.SubKind.*;
+import static org.testng.Assert.assertEquals;
+import org.testng.annotations.BeforeMethod;
+
+@Test
+public class ExecutionControlTest extends KullaTesting {
+
+    @BeforeMethod
+    @Override
+    public void setUp() {
+        setUp(new LocalExecutionControl());
+    }
+
+    public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
+        System.setProperty("LOCAL_CHECK", "TBD");
+        assertEquals(System.getProperty("LOCAL_CHECK"), "TBD");
+        assertEval("System.setProperty(\"LOCAL_CHECK\", \"local\")");
+        assertEquals(System.getProperty("LOCAL_CHECK"), "local");
+    }
+
+    public void classesDeclaration() {
+        assertEval("interface A { }");
+        assertEval("class B implements A { }");
+        assertEval("interface C extends A { }");
+        assertEval("enum D implements C { }");
+        assertEval("@interface E { }");
+        assertClasses(
+                clazz(KullaTesting.ClassType.INTERFACE, "A"),
+                clazz(KullaTesting.ClassType.CLASS, "B"),
+                clazz(KullaTesting.ClassType.INTERFACE, "C"),
+                clazz(KullaTesting.ClassType.ENUM, "D"),
+                clazz(KullaTesting.ClassType.ANNOTATION, "E"));
+        assertActiveKeys();
+    }
+
+    @Test
+    public void interfaceTest() {
+        String interfaceSource
+                = "interface A {\n"
+                + "   default int defaultMethod() { return 1; }\n"
+                + "   static int staticMethod() { return 2; }\n"
+                + "   int method();\n"
+                + "   class Inner1 {}\n"
+                + "   static class Inner2 {}\n"
+                + "}";
+        assertEval(interfaceSource);
+        assertEval("A.staticMethod();", "2");
+        String classSource
+                = "class B implements A {\n"
+                + "   public int method() { return 3; }\n"
+                + "}";
+        assertEval(classSource);
+        assertEval("B b = new B();");
+        assertEval("b.defaultMethod();", "1");
+        assertDeclareFail("B.staticMethod();",
+                new ExpectedDiagnostic("compiler.err.cant.resolve.location.args", 0, 14, 1, -1, -1, Diagnostic.Kind.ERROR));
+        assertEval("b.method();", "3");
+        assertEval("new A.Inner1();");
+        assertEval("new A.Inner2();");
+        assertEval("new B.Inner1();");
+        assertEval("new B.Inner2();");
+    }
+
+    public void variables() {
+        VarSnippet snx = varKey(assertEval("int x = 10;"));
+        VarSnippet sny = varKey(assertEval("String y = \"hi\";"));
+        VarSnippet snz = varKey(assertEval("long z;"));
+        assertVariables(variable("int", "x"), variable("String", "y"), variable("long", "z"));
+        assertVarValue(snx, "10");
+        assertVarValue(sny, "\"hi\"");
+        assertVarValue(snz, "0");
+        assertActiveKeys();
+    }
+
+    public void methodOverload() {
+        assertEval("int m() { return 1; }");
+        assertEval("int m(int x) { return 2; }");
+        assertEval("int m(String s) { return 3; }");
+        assertEval("int m(int x, int y) { return 4; }");
+        assertEval("int m(int x, String z) { return 5; }");
+        assertEval("int m(int x, String z, long g) { return 6; }");
+        assertMethods(
+                method("()int", "m"),
+                method("(int)int", "m"),
+                method("(String)int", "m"),
+                method("(int,int)int", "m"),
+                method("(int,String)int", "m"),
+                method("(int,String,long)int", "m")
+        );
+        assertEval("m();", "1");
+        assertEval("m(3);", "2");
+        assertEval("m(\"hi\");", "3");
+        assertEval("m(7, 8);", "4");
+        assertEval("m(7, \"eight\");", "5");
+        assertEval("m(7, \"eight\", 9L);", "6");
+        assertActiveKeys();
+    }
+
+    public void testExprSanity() {
+        assertEval("int x = 3;", "3");
+        assertEval("int y = 4;", "4");
+        assertEval("x + y;", "7");
+        assertActiveKeys();
+    }
+
+    public void testImportOnDemand() {
+        assertImportKeyMatch("import java.util.*;", "java.util.*", TYPE_IMPORT_ON_DEMAND_SUBKIND, added(VALID));
+        assertEval("List<Integer> list = new ArrayList<>();");
+        assertEval("list.add(45);");
+        assertEval("list.size();", "1");
+    }
+}
--- a/langtools/test/jdk/jshell/KullaTesting.java	Fri May 20 17:00:03 2016 -0700
+++ b/langtools/test/jdk/jshell/KullaTesting.java	Sat May 21 22:32:08 2016 -0700
@@ -72,6 +72,7 @@
 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 {
 
@@ -166,6 +167,21 @@
         classpath = new ArrayList<>();
     }
 
+    public void setUp(ExecutionControl ec) {
+        inStream = new TestingInputStream();
+        outStream = new ByteArrayOutputStream();
+        errStream = new ByteArrayOutputStream();
+        state = JShell.builder()
+                .executionEngine(ec)
+                .in(inStream)
+                .out(new PrintStream(outStream))
+                .err(new PrintStream(errStream))
+                .build();
+        allSnippets = new LinkedHashSet<>();
+        idToSnippet = new LinkedHashMap<>();
+        classpath = new ArrayList<>();
+    }
+
     @AfterMethod
     public void tearDown() {
         if (state != null) state.close();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/LocalExecutionControl.java	Sat May 21 22:32:08 2016 -0700
@@ -0,0 +1,312 @@
+/*
+ * 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) {
+                    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();
+        }
+    }
+}