8131023: JShell: System.in does not work
authorjlahoda
Thu, 01 Sep 2016 10:30:50 +0200
changeset 40767 c7908e8c786b
parent 40766 5e7d12c4fe70
child 40768 8b6a878d8773
8131023: JShell: System.in does not work Summary: Pass user input to snippets/remote agent Reviewed-by: rfield
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java
langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DemultiplexInput.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIDefaultExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/MultiplexingOutputStream.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/PipeInputStream.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java
langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java
langtools/test/jdk/jshell/ReplToolTesting.java
langtools/test/jdk/jshell/StartOptionTest.java
langtools/test/jdk/jshell/UserInputTest.java
langtools/test/jdk/jshell/UserJDIUserRemoteTest.java
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Thu Sep 01 10:30:50 2016 +0200
@@ -57,6 +57,8 @@
 import jdk.internal.jline.console.KeyMap;
 import jdk.internal.jline.console.UserInterruptException;
 import jdk.internal.jline.console.completer.Completer;
+import jdk.internal.jline.console.history.History;
+import jdk.internal.jline.console.history.MemoryHistory;
 import jdk.internal.jline.extra.EditingHistory;
 import jdk.internal.jshell.tool.StopDetectingInputStream.State;
 
@@ -68,6 +70,7 @@
     final StopDetectingInputStream input;
     final ConsoleReader in;
     final EditingHistory history;
+    final MemoryHistory userInputHistory = new MemoryHistory();
 
     String prefix = "";
 
@@ -299,6 +302,9 @@
     }
 
     public void beforeUserCode() {
+        synchronized (this) {
+            inputBytes = null;
+        }
         input.setState(State.BUFFER);
     }
 
@@ -380,6 +386,36 @@
         }
     }
 
+    private byte[] inputBytes;
+    private int inputBytesPointer;
+
+    @Override
+    public synchronized int readUserInput() {
+        while (inputBytes == null || inputBytes.length <= inputBytesPointer) {
+            boolean prevHandleUserInterrupt = in.getHandleUserInterrupt();
+            History prevHistory = in.getHistory();
+
+            try {
+                input.setState(State.WAIT);
+                in.setHandleUserInterrupt(true);
+                in.setHistory(userInputHistory);
+                inputBytes = (in.readLine("") + System.getProperty("line.separator")).getBytes();
+                inputBytesPointer = 0;
+            } catch (IOException ex) {
+                ex.printStackTrace();
+                return -1;
+            } catch (UserInterruptException ex) {
+                repl.state.stop();
+                return -1;
+            } finally {
+                in.setHistory(prevHistory);
+                in.setHandleUserInterrupt(prevHandleUserInterrupt);
+                input.setState(State.BUFFER);
+            }
+        }
+        return inputBytes[inputBytesPointer++];
+    }
+
     /**
      * A possible action which the user can choose to perform.
      */
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java	Thu Sep 01 10:30:50 2016 +0200
@@ -54,6 +54,8 @@
 
     public abstract void replaceLastHistoryEntry(String source);
 
+    public abstract int readUserInput();
+
     class InputInterruptedException extends Exception {
         private static final long serialVersionUID = 1L;
     }
--- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Thu Sep 01 10:30:50 2016 +0200
@@ -26,7 +26,6 @@
 package jdk.internal.jshell.tool;
 
 import java.io.BufferedWriter;
-import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
@@ -142,11 +141,30 @@
     /**
      * The constructor for the tool (used by tool launch via main and by test
      * harnesses to capture ins and outs.
+     * @param in command line input -- snippets, commands and user input
+     * @param cmdout command line output, feedback including errors
+     * @param cmderr start-up errors and debugging info
+     * @param console console control interaction
+     * @param userout code execution output  -- System.out.printf("hi")
+     * @param usererr code execution error stream  -- System.err.printf("Oops")
+     * @param prefs preferences to use
+     * @param locale locale to use
+     */
+    public JShellTool(InputStream in, PrintStream cmdout, PrintStream cmderr,
+            PrintStream console,
+            PrintStream userout, PrintStream usererr,
+            Preferences prefs, Locale locale) {
+        this(in, cmdout, cmderr, console, null, userout, usererr, prefs, locale);
+    }
+
+    /**
+     * The constructor for the tool (used by tool launch via main and by test
+     * harnesses to capture ins and outs.
      * @param cmdin command line input -- snippets and commands
      * @param cmdout command line output, feedback including errors
      * @param cmderr start-up errors and debugging info
      * @param console console control interaction
-     * @param userin code execution input (not yet functional)
+     * @param userin code execution input, or null to use IOContext
      * @param userout code execution output  -- System.out.printf("hi")
      * @param usererr code execution error stream  -- System.err.printf("Oops")
      * @param prefs preferences to use
@@ -160,7 +178,12 @@
         this.cmdout = cmdout;
         this.cmderr = cmderr;
         this.console = console;
-        this.userin = userin;
+        this.userin = userin != null ? userin : new InputStream() {
+            @Override
+            public int read() throws IOException {
+                return input.readUserInput();
+            }
+        };
         this.userout = userout;
         this.usererr = usererr;
         this.prefs = prefs;
@@ -452,7 +475,7 @@
      */
     public static void main(String[] args) throws Exception {
         new JShellTool(System.in, System.out, System.err, System.out,
-                 new ByteArrayInputStream(new byte[0]), System.out, System.err,
+                 System.out, System.err,
                  Preferences.userRoot().node("tool/JShell"),
                  Locale.getDefault())
                 .start(args);
@@ -2621,6 +2644,11 @@
     public void close() {
         scannerIn.close();
     }
+
+    @Override
+    public int readUserInput() {
+        return -1;
+    }
 }
 
 class FileScannerIOContext extends ScannerIOContext {
@@ -2659,4 +2687,9 @@
     @Override
     public void close() {
     }
+
+    @Override
+    public int readUserInput() {
+        return -1;
+    }
 }
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DemultiplexInput.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/DemultiplexInput.java	Thu Sep 01 10:30:50 2016 +0200
@@ -40,15 +40,14 @@
 class DemultiplexInput extends Thread {
 
     private final DataInputStream delegate;
-    private final PipeInputStream command;
     private final Map<String, OutputStream> io;
+    private final Iterable<OutputStream> closeList;
 
-    DemultiplexInput(InputStream input, PipeInputStream command,
-            Map<String, OutputStream> io) {
+    DemultiplexInput(InputStream input, Map<String, OutputStream> io, Iterable<OutputStream> closeList) {
         super("output reader");
         this.delegate = new DataInputStream(input);
-        this.command = command;
         this.io = io;
+        this.closeList = closeList;
     }
 
     @Override
@@ -65,23 +64,23 @@
                 byte[] data = new byte[dataLen];
                 DemultiplexInput.this.delegate.readFully(data);
                 String chan = new String(name, "UTF-8");
-                if (chan.equals("command")) {
-                    for (byte b : data) {
-                        command.write(Byte.toUnsignedInt(b));
-                    }
+                OutputStream out = io.get(chan);
+                if (out == null) {
+                    debug("Unexpected channel name: %s", chan);
                 } else {
-                    OutputStream out = io.get(chan);
-                    if (out == null) {
-                        debug("Unexpected channel name: %s", chan);
-                    } else {
-                        out.write(data);
-                    }
+                    out.write(data);
                 }
             }
         } catch (IOException ex) {
             debug(ex, "Failed reading output");
         } finally {
-            command.close();
+            for (OutputStream out : closeList) {
+                try {
+                    out.close();
+                } catch (IOException ex) {
+                    debug(ex, "Failed reading output");
+                }
+            }
         }
     }
 
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIDefaultExecutionControl.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/JDIDefaultExecutionControl.java	Thu Sep 01 10:30:50 2016 +0200
@@ -25,9 +25,9 @@
 package jdk.jshell.execution;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
-import java.io.ObjectOutputStream;
 import java.io.OutputStream;
 import java.net.ServerSocket;
 import java.net.Socket;
@@ -48,7 +48,7 @@
 import com.sun.jdi.VirtualMachine;
 import jdk.jshell.spi.ExecutionControl;
 import jdk.jshell.spi.ExecutionEnv;
-import static jdk.jshell.execution.Util.remoteInput;
+import static jdk.jshell.execution.Util.remoteInputOutput;
 
 /**
  * The implementation of {@link jdk.jshell.spi.ExecutionControl} that the
@@ -109,7 +109,7 @@
      * @return the channel
      * @throws IOException if there are errors in set-up
      */
-    private static JDIDefaultExecutionControl create(ExecutionEnv env,
+    private static ExecutionControl create(ExecutionEnv env,
             boolean isLaunch, String host) throws IOException {
         try (final ServerSocket listener = new ServerSocket(0)) {
             // timeout after 60 seconds
@@ -122,10 +122,6 @@
             VirtualMachine vm = jdii.vm();
             Process process = jdii.process();
 
-            // Forward input to the remote agent
-            Util.forwardInputToRemote(env.userIn(), process.getOutputStream(),
-                    ex -> debug(ex, "input forwarding failure"));
-
             List<Consumer<String>> deathListeners = new ArrayList<>();
             deathListeners.add(s -> env.closeDown());
             Util.detectJDIExitEvent(vm, s -> {
@@ -138,12 +134,13 @@
             // output.
             Socket socket = listener.accept();
             // out before in -- match remote creation so we don't hang
-            ObjectOutput cmdout = new ObjectOutputStream(socket.getOutputStream());
-            Map<String, OutputStream> io = new HashMap<>();
-            io.put("out", env.userOut());
-            io.put("err", env.userErr());
-            ObjectInput cmdin = remoteInput(socket.getInputStream(), io);
-            return new JDIDefaultExecutionControl(cmdout, cmdin, vm, process, deathListeners);
+            OutputStream out = socket.getOutputStream();
+            Map<String, OutputStream> outputs = new HashMap<>();
+            outputs.put("out", env.userOut());
+            outputs.put("err", env.userErr());
+            Map<String, InputStream> input = new HashMap<>();
+            input.put("in", env.userIn());
+            return remoteInputOutput(socket.getInputStream(), out, outputs, input, (objIn, objOut) -> new JDIDefaultExecutionControl(objOut, objIn, vm, process, deathListeners));
         }
     }
 
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/MultiplexingOutputStream.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/MultiplexingOutputStream.java	Thu Sep 01 10:30:50 2016 +0200
@@ -50,13 +50,7 @@
 
     @Override
     public void write(int b) throws IOException {
-        synchronized (delegate) {
-            delegate.write(name.length); //assuming the len is small enough to fit into byte
-            delegate.write(name);
-            delegate.write(1);
-            delegate.write(b);
-            delegate.flush();
-        }
+        write(new byte[] {(byte) b});
     }
 
     @Override
@@ -65,10 +59,12 @@
             int i = 0;
             while (len > 0) {
                 int size = Math.min(PACKET_SIZE, len);
-                delegate.write(name.length); //assuming the len is small enough to fit into byte
-                delegate.write(name);
-                delegate.write(size);
-                delegate.write(b, off + i, size);
+                byte[] data = new byte[name.length + 1 + size + 1];
+                data[0] = (byte) name.length; //assuming the len is small enough to fit into byte
+                System.arraycopy(name, 0, data, 1, name.length);
+                data[name.length + 1] = (byte) size;
+                System.arraycopy(b, off + i, data, name.length + 2, size);
+                delegate.write(data);
                 i += size;
                 len -= size;
             }
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/PipeInputStream.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/PipeInputStream.java	Thu Sep 01 10:30:50 2016 +0200
@@ -24,7 +24,9 @@
  */
 package jdk.jshell.execution;
 
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 
 /**
  *
@@ -39,7 +41,10 @@
     private boolean closed;
 
     @Override
-    public synchronized int read() {
+    public synchronized int read() throws IOException {
+        if (start == end) {
+            inputNeeded();
+        }
         while (start == end) {
             if (closed) {
                 return -1;
@@ -57,7 +62,9 @@
         }
     }
 
-    public synchronized void write(int b) {
+    protected void inputNeeded() throws IOException {}
+
+    private synchronized void write(int b) {
         if (closed) {
             throw new IllegalStateException("Already closed.");
         }
@@ -85,4 +92,22 @@
         notifyAll();
     }
 
+    public OutputStream createOutput() {
+        return new OutputStream() {
+            @Override public void write(int b) throws IOException {
+                PipeInputStream.this.write(b);
+            }
+            @Override
+            public void write(byte[] b, int off, int len) throws IOException {
+                for (int i = 0 ; i < len ; i++) {
+                    write(Byte.toUnsignedInt(b[off + i]));
+                }
+            }
+            @Override
+            public void close() throws IOException {
+                PipeInputStream.this.close();
+            }
+        };
+    }
+
 }
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java	Thu Sep 01 10:30:50 2016 +0200
@@ -61,10 +61,12 @@
         Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
         InputStream inStream = socket.getInputStream();
         OutputStream outStream = socket.getOutputStream();
-        Map<String, Consumer<OutputStream>> chans = new HashMap<>();
-        chans.put("out", st -> System.setOut(new PrintStream(st, true)));
-        chans.put("err", st -> System.setErr(new PrintStream(st, true)));
-        forwardExecutionControlAndIO(new RemoteExecutionControl(), inStream, outStream, chans);
+        Map<String, Consumer<OutputStream>> outputs = new HashMap<>();
+        outputs.put("out", st -> System.setOut(new PrintStream(st, true)));
+        outputs.put("err", st -> System.setErr(new PrintStream(st, true)));
+        Map<String, Consumer<InputStream>> input = new HashMap<>();
+        input.put("in", st -> System.setIn(st));
+        forwardExecutionControlAndIO(new RemoteExecutionControl(), inStream, outStream, outputs, input);
     }
 
     // These three variables are used by the main JShell process in interrupting
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java	Thu Sep 01 10:30:50 2016 +0200
@@ -25,6 +25,7 @@
 package jdk.jshell.execution;
 
 import jdk.jshell.spi.ExecutionEnv;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.ObjectInput;
@@ -32,9 +33,13 @@
 import java.io.ObjectOutput;
 import java.io.ObjectOutputStream;
 import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
+
 import com.sun.jdi.VirtualMachine;
 import jdk.jshell.spi.ExecutionControl;
 
@@ -99,21 +104,40 @@
      * instance, then responses back on the output.
      * @param ec the direct instance of {@link ExecutionControl} to process commands
      * @param inStream the stream from which to create the command input
-     * @param outStream the stream that will carry {@code System.out},
-     * {@code System.err}, any specified auxiliary channels, and the
-     * command response output.
-     * @param streamMap a map between names of additional streams to carry and setters
-     * for the stream
+     * @param outStream the stream that will carry any specified auxiliary channels (like
+     *                  {@code System.out} and {@code System.err}), and the command response output.
+     * @param outputStreamMap a map between names of additional streams to carry and setters
+     *                        for the stream. Names starting with '$' are reserved for internal use.
+     * @param inputStreamMap a map between names of additional streams to carry and setters
+     *                       for the stream. Names starting with '$' are reserved for internal use.
      * @throws IOException if there are errors using the passed streams
      */
     public static void forwardExecutionControlAndIO(ExecutionControl ec,
             InputStream inStream, OutputStream outStream,
-            Map<String, Consumer<OutputStream>> streamMap) throws IOException {
-        ObjectInputStream cmdIn = new ObjectInputStream(inStream);
-        for (Entry<String, Consumer<OutputStream>> e : streamMap.entrySet()) {
+            Map<String, Consumer<OutputStream>> outputStreamMap,
+            Map<String, Consumer<InputStream>> inputStreamMap) throws IOException {
+        for (Entry<String, Consumer<OutputStream>> e : outputStreamMap.entrySet()) {
             e.getValue().accept(multiplexingOutputStream(e.getKey(), outStream));
         }
-        ObjectOutputStream cmdOut = new ObjectOutputStream(multiplexingOutputStream("command", outStream));
+
+        ObjectOutputStream cmdOut = new ObjectOutputStream(multiplexingOutputStream("$command", outStream));
+        PipeInputStream cmdInPipe = new PipeInputStream();
+        Map<String, OutputStream> inputs = new HashMap<>();
+        inputs.put("$command", cmdInPipe.createOutput());
+        for (Entry<String, Consumer<InputStream>> e : inputStreamMap.entrySet()) {
+            OutputStream inputSignal = multiplexingOutputStream("$" + e.getKey() + "-input-requested", outStream);
+            PipeInputStream inputPipe = new PipeInputStream() {
+                @Override protected void inputNeeded() throws IOException {
+                    inputSignal.write('1');
+                    inputSignal.flush();
+                }
+            };
+            inputs.put(e.getKey(), inputPipe.createOutput());
+            e.getValue().accept(inputPipe);
+        }
+        new DemultiplexInput(inStream, inputs, inputs.values()).start();
+        ObjectInputStream cmdIn = new ObjectInputStream(cmdInPipe);
+
         forwardExecutionControl(ec, cmdIn, cmdOut);
     }
 
@@ -122,18 +146,41 @@
     }
 
     /**
-     * Reads from an InputStream which has been packetized and write its contents
-     * to the out and err OutputStreams; Copies the command stream.
+     * Creates an ExecutionControl for given packetized input and output. The given InputStream
+     * is de-packetized, and content forwarded to ObjectInput and given OutputStreams. The ObjectOutput
+     * and values read from the given InputStream are packetized and sent to the given OutputStream.
+     *
      * @param input the packetized input stream
-     * @param streamMap a map between stream names and the output streams to forward
-     * @return the command stream
+     * @param output the packetized output stream
+     * @param outputStreamMap a map between stream names and the output streams to forward.
+     *                        Names starting with '$' are reserved for internal use.
+     * @param inputStreamMap a map between stream names and the input streams to forward.
+     *                       Names starting with '$' are reserved for internal use.
+     * @param factory to create the ExecutionControl from ObjectInput and ObjectOutput.
+     * @return the created ExecutionControl
      * @throws IOException if setting up the streams raised an exception
      */
-    public static ObjectInput remoteInput(InputStream input,
-            Map<String, OutputStream> streamMap) throws IOException {
+    public static ExecutionControl remoteInputOutput(InputStream input, OutputStream output,
+            Map<String, OutputStream> outputStreamMap, Map<String, InputStream> inputStreamMap,
+            BiFunction<ObjectInput, ObjectOutput, ExecutionControl> factory) throws IOException {
+        Map<String, OutputStream> augmentedStreamMap = new HashMap<>(outputStreamMap);
+        ObjectOutput commandOut = new ObjectOutputStream(Util.multiplexingOutputStream("$command", output));
+        for (Entry<String, InputStream> e : inputStreamMap.entrySet()) {
+            InputStream  in = e.getValue();
+            OutputStream inTarget = Util.multiplexingOutputStream(e.getKey(), output);
+            augmentedStreamMap.put("$" + e.getKey() + "-input-requested", new OutputStream() {
+                @Override
+                public void write(int b) throws IOException {
+                    //value ignored, just a trigger to read from the input
+                    inTarget.write(in.read());
+                }
+            });
+        }
         PipeInputStream commandIn = new PipeInputStream();
-        new DemultiplexInput(input, commandIn, streamMap).start();
-        return new ObjectInputStream(commandIn);
+        OutputStream commandInTarget = commandIn.createOutput();
+        augmentedStreamMap.put("$command", commandInTarget);
+        new DemultiplexInput(input, augmentedStreamMap, Arrays.asList(commandInTarget)).start();
+        return factory.apply(new ObjectInputStream(commandIn), commandOut);
     }
 
     /**
@@ -151,32 +198,4 @@
         }
     }
 
-    /**
-     * Creates a Thread that will ship all input to the remote agent.
-     *
-     * @param inputStream the user input
-     * @param outStream the input to the remote agent
-     * @param handler a failure handler
-     */
-    public static void forwardInputToRemote(final InputStream inputStream,
-            final OutputStream outStream, final Consumer<Exception> handler) {
-        Thread thr = new Thread("input reader") {
-            @Override
-            public void run() {
-                try {
-                    byte[] buf = new byte[256];
-                    int cnt;
-                    while ((cnt = inputStream.read(buf)) != -1) {
-                        outStream.write(buf, 0, cnt);
-                        outStream.flush();
-                    }
-                } catch (Exception ex) {
-                    handler.accept(ex);
-                }
-            }
-        };
-        thr.setPriority(Thread.MAX_PRIORITY - 1);
-        thr.start();
-    }
-
 }
--- a/langtools/test/jdk/jshell/ReplToolTesting.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/test/jdk/jshell/ReplToolTesting.java	Thu Sep 01 10:30:50 2016 +0200
@@ -247,7 +247,6 @@
                 new PrintStream(cmdout),
                 new PrintStream(cmderr),
                 new PrintStream(console),
-                userin,
                 new PrintStream(userout),
                 new PrintStream(usererr),
                 prefs,
@@ -463,7 +462,7 @@
 
     private List<String> computeCompletions(String code, boolean isSmart) {
         JShellTool js = this.repl != null ? this.repl
-                                      : new JShellTool(null, null, null, null, null, null, null, prefs, Locale.ROOT);
+                                      : new JShellTool(null, null, null, null, null, null, prefs, Locale.ROOT);
         int cursor =  code.indexOf('|');
         code = code.replace("|", "");
         assertTrue(cursor > -1, "'|' not found: " + code);
--- a/langtools/test/jdk/jshell/StartOptionTest.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/test/jdk/jshell/StartOptionTest.java	Thu Sep 01 10:30:50 2016 +0200
@@ -63,7 +63,6 @@
                 new PrintStream(cmdout),
                 new PrintStream(cmderr),
                 new PrintStream(console),
-                new TestingInputStream(),
                 new PrintStream(userout),
                 new PrintStream(usererr),
                 new ReplToolTesting.MemoryPreferences(),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/UserInputTest.java	Thu Sep 01 10:30:50 2016 +0200
@@ -0,0 +1,44 @@
+/*
+ * 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 8131023
+ * @summary Verify that the user's code can read System.in
+ * @build KullaTesting TestingInputStream
+ * @run testng UserInputTest
+ */
+
+import org.testng.annotations.Test;
+
+@Test
+public class UserInputTest extends KullaTesting {
+
+    public void testReadInput() {
+        setInput("AB\n");
+        assertEval("System.in.read()", "65");
+        setInput("BC\n");
+        assertEval("System.in.read()", "66");
+    }
+
+}
--- a/langtools/test/jdk/jshell/UserJDIUserRemoteTest.java	Thu Sep 01 13:18:42 2016 +0800
+++ b/langtools/test/jdk/jshell/UserJDIUserRemoteTest.java	Thu Sep 01 10:30:50 2016 +0200
@@ -37,7 +37,6 @@
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
-import java.io.ObjectOutputStream;
 import java.net.ServerSocket;
 import java.util.ArrayList;
 import java.util.List;
@@ -62,7 +61,7 @@
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.fail;
 import static jdk.jshell.execution.Util.forwardExecutionControlAndIO;
-import static jdk.jshell.execution.Util.remoteInput;
+import static jdk.jshell.execution.Util.remoteInputOutput;
 
 @Test
 public class UserJDIUserRemoteTest extends ExecutionControlTestBase {
@@ -146,7 +145,7 @@
      * @return the channel
      * @throws IOException if there are errors in set-up
      */
-    static MyExecutionControl make(ExecutionEnv env, UserJDIUserRemoteTest test) throws IOException {
+    static ExecutionControl make(ExecutionEnv env, UserJDIUserRemoteTest test) throws IOException {
         try (final ServerSocket listener = new ServerSocket(0)) {
             // timeout after 60 seconds
             listener.setSoTimeout(60000);
@@ -175,13 +174,14 @@
             // output.
             Socket socket = listener.accept();
             // out before in -- match remote creation so we don't hang
-            ObjectOutput cmdout = new ObjectOutputStream(socket.getOutputStream());
-            Map<String, OutputStream> io = new HashMap<>();
-            io.put("out", env.userOut());
-            io.put("err", env.userErr());
-            io.put("aux", test.auxStream);
-            ObjectInput cmdin = remoteInput(socket.getInputStream(), io);
-            MyExecutionControl myec = new MyExecutionControl(cmdout, cmdin, vm, process, deathListeners);
+            OutputStream out = socket.getOutputStream();
+            Map<String, OutputStream> outputs = new HashMap<>();
+            outputs.put("out", env.userOut());
+            outputs.put("err", env.userErr());
+            outputs.put("aux", test.auxStream);
+            Map<String, InputStream> input = new HashMap<>();
+            input.put("in", env.userIn());
+            ExecutionControl myec = remoteInputOutput(socket.getInputStream(), out, outputs, input, (objIn, objOut) -> new MyExecutionControl(objOut, objIn, vm, process, deathListeners));
             test.currentEC = myec;
             return myec;
         }
@@ -255,11 +255,13 @@
             Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
             InputStream inStream = socket.getInputStream();
             OutputStream outStream = socket.getOutputStream();
-            Map<String, Consumer<OutputStream>> chans = new HashMap<>();
-            chans.put("out", st -> System.setOut(new PrintStream(st, true)));
-            chans.put("err", st -> System.setErr(new PrintStream(st, true)));
-            chans.put("aux", st -> { auxPrint = new PrintStream(st, true); });
-            forwardExecutionControlAndIO(new MyRemoteExecutionControl(), inStream, outStream, chans);
+            Map<String, Consumer<OutputStream>> outputs = new HashMap<>();
+            outputs.put("out", st -> System.setOut(new PrintStream(st, true)));
+            outputs.put("err", st -> System.setErr(new PrintStream(st, true)));
+            outputs.put("aux", st -> { auxPrint = new PrintStream(st, true); });
+            Map<String, Consumer<InputStream>> input = new HashMap<>();
+            input.put("in", st -> System.setIn(st));
+            forwardExecutionControlAndIO(new MyRemoteExecutionControl(), inStream, outStream, outputs, input);
         } catch (Throwable ex) {
             throw ex;
         }