src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java
changeset 47216 71c04702a3d5
parent 43770 a321bed02000
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jshell.execution;
+
+import jdk.jshell.spi.ExecutionEnv;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+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;
+import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
+
+
+/**
+ * Miscellaneous utility methods for setting-up implementations of
+ * {@link ExecutionControl}. Particularly implementations with remote
+ * execution.
+ *
+ * @author Jan Lahoda
+ * @author Robert Field
+ * @since 9
+ */
+public class Util {
+
+    private static final int TAG_DATA = 0;
+    private static final int TAG_CLOSED = 1;
+    private static final int TAG_EXCEPTION = 2;
+
+    // never instantiated
+    private Util() {}
+
+    /**
+     * Forward commands from the input to the specified {@link ExecutionControl}
+     * instance, then responses back on the output.
+     * @param ec the direct instance of {@link ExecutionControl} to process commands
+     * @param in the command input
+     * @param out the command response output
+     */
+    public static void forwardExecutionControl(ExecutionControl ec,
+            ObjectInput in, ObjectOutput out) {
+        new ExecutionControlForwarder(ec, in, out).commandLoop();
+    }
+
+    /**
+     * Forward commands from the input to the specified {@link ExecutionControl}
+     * instance, then responses back on the output.
+     * @param ec the direct instance of {@link ExecutionControl} to process commands
+     * @param inStream the stream from which to create the command input
+     * @param outStream the stream that will carry 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>> 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));
+        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();
+                }
+                @Override
+                public synchronized int read() throws IOException {
+                    int tag = super.read();
+                    switch (tag) {
+                        case TAG_DATA: return super.read();
+                        case TAG_CLOSED: close(); return -1;
+                        case TAG_EXCEPTION:
+                            int len = (super.read() << 0) + (super.read() << 8) + (super.read() << 16) + (super.read() << 24);
+                            byte[] message = new byte[len];
+                            for (int i = 0; i < len; i++) {
+                                message[i] = (byte) super.read();
+                            }
+                            throw new IOException(new String(message, "UTF-8"));
+                        case -1:
+                            return -1;
+                        default:
+                            throw new IOException("Internal error: unrecognized message tag: " + tag);
+                    }
+                }
+            };
+            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);
+    }
+
+    static OutputStream multiplexingOutputStream(String label, OutputStream outputStream) {
+        return new MultiplexingOutputStream(label, outputStream);
+    }
+
+    /**
+     * 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 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 ExecutionControl remoteInputOutput(InputStream input, OutputStream output,
+            Map<String, OutputStream> outputStreamMap, Map<String, InputStream> inputStreamMap,
+            BiFunction<ObjectInput, ObjectOutput, ExecutionControl> factory) throws IOException {
+        ExecutionControl[] result = new ExecutionControl[1];
+        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
+                    try {
+                        int r = in.read();
+                        if (r == (-1)) {
+                            inTarget.write(TAG_CLOSED);
+                        } else {
+                            inTarget.write(new byte[] {TAG_DATA, (byte) r});
+                        }
+                    } catch (InterruptedIOException exc) {
+                        try {
+                            result[0].stop();
+                        } catch (ExecutionControlException ex) {
+                            debug(ex, "$" + e.getKey() + "-input-requested.write");
+                        }
+                    } catch (IOException exc) {
+                        byte[] message = exc.getMessage().getBytes("UTF-8");
+                        inTarget.write(TAG_EXCEPTION);
+                        inTarget.write((message.length >>  0) & 0xFF);
+                        inTarget.write((message.length >>  8) & 0xFF);
+                        inTarget.write((message.length >> 16) & 0xFF);
+                        inTarget.write((message.length >> 24) & 0xFF);
+                        inTarget.write(message);
+                    }
+                }
+            });
+        }
+        PipeInputStream commandIn = new PipeInputStream();
+        OutputStream commandInTarget = commandIn.createOutput();
+        augmentedStreamMap.put("$command", commandInTarget);
+        new DemultiplexInput(input, augmentedStreamMap, Arrays.asList(commandInTarget)).start();
+        return result[0] = factory.apply(new ObjectInputStream(commandIn), commandOut);
+    }
+
+    /**
+     * Monitor the JDI event stream for {@link com.sun.jdi.event.VMDeathEvent}
+     * and {@link com.sun.jdi.event.VMDisconnectEvent}. If encountered, invokes
+     * {@code unbiddenExitHandler}.
+     *
+     * @param vm the virtual machine to check
+     * @param unbiddenExitHandler the handler, which will accept the exit
+     * information
+     */
+    public static void detectJdiExitEvent(VirtualMachine vm, Consumer<String> unbiddenExitHandler) {
+        if (vm.canBeModified()) {
+            new JdiEventHandler(vm, unbiddenExitHandler).start();
+        }
+    }
+
+    /**
+     * Log a serious unexpected internal exception.
+     *
+     * @param ex the exception
+     * @param where a description of the context of the exception
+     */
+    private static void debug(Throwable ex, String where) {
+        // Reserved for future logging
+    }
+}