langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java
changeset 38535 4a25025e0b0d
parent 37745 4b6b59f8e327
child 38836 b09d1cfbf28c
--- 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() {