src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java
changeset 47216 71c04702a3d5
parent 43770 a321bed02000
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     1 /*
       
     2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package jdk.jshell.execution;
       
    26 
       
    27 import jdk.jshell.spi.ExecutionEnv;
       
    28 
       
    29 import java.io.IOException;
       
    30 import java.io.InputStream;
       
    31 import java.io.InterruptedIOException;
       
    32 import java.io.ObjectInput;
       
    33 import java.io.ObjectInputStream;
       
    34 import java.io.ObjectOutput;
       
    35 import java.io.ObjectOutputStream;
       
    36 import java.io.OutputStream;
       
    37 import java.util.Arrays;
       
    38 import java.util.HashMap;
       
    39 import java.util.Map;
       
    40 import java.util.Map.Entry;
       
    41 import java.util.function.BiFunction;
       
    42 import java.util.function.Consumer;
       
    43 
       
    44 import com.sun.jdi.VirtualMachine;
       
    45 import jdk.jshell.spi.ExecutionControl;
       
    46 import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
       
    47 
       
    48 
       
    49 /**
       
    50  * Miscellaneous utility methods for setting-up implementations of
       
    51  * {@link ExecutionControl}. Particularly implementations with remote
       
    52  * execution.
       
    53  *
       
    54  * @author Jan Lahoda
       
    55  * @author Robert Field
       
    56  * @since 9
       
    57  */
       
    58 public class Util {
       
    59 
       
    60     private static final int TAG_DATA = 0;
       
    61     private static final int TAG_CLOSED = 1;
       
    62     private static final int TAG_EXCEPTION = 2;
       
    63 
       
    64     // never instantiated
       
    65     private Util() {}
       
    66 
       
    67     /**
       
    68      * Forward commands from the input to the specified {@link ExecutionControl}
       
    69      * instance, then responses back on the output.
       
    70      * @param ec the direct instance of {@link ExecutionControl} to process commands
       
    71      * @param in the command input
       
    72      * @param out the command response output
       
    73      */
       
    74     public static void forwardExecutionControl(ExecutionControl ec,
       
    75             ObjectInput in, ObjectOutput out) {
       
    76         new ExecutionControlForwarder(ec, in, out).commandLoop();
       
    77     }
       
    78 
       
    79     /**
       
    80      * Forward commands from the input to the specified {@link ExecutionControl}
       
    81      * instance, then responses back on the output.
       
    82      * @param ec the direct instance of {@link ExecutionControl} to process commands
       
    83      * @param inStream the stream from which to create the command input
       
    84      * @param outStream the stream that will carry any specified auxiliary channels (like
       
    85      *                  {@code System.out} and {@code System.err}), and the command response output.
       
    86      * @param outputStreamMap a map between names of additional streams to carry and setters
       
    87      *                        for the stream. Names starting with '$' are reserved for internal use.
       
    88      * @param inputStreamMap a map between names of additional streams to carry and setters
       
    89      *                       for the stream. Names starting with '$' are reserved for internal use.
       
    90      * @throws IOException if there are errors using the passed streams
       
    91      */
       
    92     public static void forwardExecutionControlAndIO(ExecutionControl ec,
       
    93             InputStream inStream, OutputStream outStream,
       
    94             Map<String, Consumer<OutputStream>> outputStreamMap,
       
    95             Map<String, Consumer<InputStream>> inputStreamMap) throws IOException {
       
    96         for (Entry<String, Consumer<OutputStream>> e : outputStreamMap.entrySet()) {
       
    97             e.getValue().accept(multiplexingOutputStream(e.getKey(), outStream));
       
    98         }
       
    99 
       
   100         ObjectOutputStream cmdOut = new ObjectOutputStream(multiplexingOutputStream("$command", outStream));
       
   101         PipeInputStream cmdInPipe = new PipeInputStream();
       
   102         Map<String, OutputStream> inputs = new HashMap<>();
       
   103         inputs.put("$command", cmdInPipe.createOutput());
       
   104         for (Entry<String, Consumer<InputStream>> e : inputStreamMap.entrySet()) {
       
   105             OutputStream inputSignal = multiplexingOutputStream("$" + e.getKey() + "-input-requested", outStream);
       
   106             PipeInputStream inputPipe = new PipeInputStream() {
       
   107                 @Override protected void inputNeeded() throws IOException {
       
   108                     inputSignal.write('1');
       
   109                     inputSignal.flush();
       
   110                 }
       
   111                 @Override
       
   112                 public synchronized int read() throws IOException {
       
   113                     int tag = super.read();
       
   114                     switch (tag) {
       
   115                         case TAG_DATA: return super.read();
       
   116                         case TAG_CLOSED: close(); return -1;
       
   117                         case TAG_EXCEPTION:
       
   118                             int len = (super.read() << 0) + (super.read() << 8) + (super.read() << 16) + (super.read() << 24);
       
   119                             byte[] message = new byte[len];
       
   120                             for (int i = 0; i < len; i++) {
       
   121                                 message[i] = (byte) super.read();
       
   122                             }
       
   123                             throw new IOException(new String(message, "UTF-8"));
       
   124                         case -1:
       
   125                             return -1;
       
   126                         default:
       
   127                             throw new IOException("Internal error: unrecognized message tag: " + tag);
       
   128                     }
       
   129                 }
       
   130             };
       
   131             inputs.put(e.getKey(), inputPipe.createOutput());
       
   132             e.getValue().accept(inputPipe);
       
   133         }
       
   134         new DemultiplexInput(inStream, inputs, inputs.values()).start();
       
   135         ObjectInputStream cmdIn = new ObjectInputStream(cmdInPipe);
       
   136 
       
   137         forwardExecutionControl(ec, cmdIn, cmdOut);
       
   138     }
       
   139 
       
   140     static OutputStream multiplexingOutputStream(String label, OutputStream outputStream) {
       
   141         return new MultiplexingOutputStream(label, outputStream);
       
   142     }
       
   143 
       
   144     /**
       
   145      * Creates an ExecutionControl for given packetized input and output. The given InputStream
       
   146      * is de-packetized, and content forwarded to ObjectInput and given OutputStreams. The ObjectOutput
       
   147      * and values read from the given InputStream are packetized and sent to the given OutputStream.
       
   148      *
       
   149      * @param input the packetized input stream
       
   150      * @param output the packetized output stream
       
   151      * @param outputStreamMap a map between stream names and the output streams to forward.
       
   152      *                        Names starting with '$' are reserved for internal use.
       
   153      * @param inputStreamMap a map between stream names and the input streams to forward.
       
   154      *                       Names starting with '$' are reserved for internal use.
       
   155      * @param factory to create the ExecutionControl from ObjectInput and ObjectOutput.
       
   156      * @return the created ExecutionControl
       
   157      * @throws IOException if setting up the streams raised an exception
       
   158      */
       
   159     public static ExecutionControl remoteInputOutput(InputStream input, OutputStream output,
       
   160             Map<String, OutputStream> outputStreamMap, Map<String, InputStream> inputStreamMap,
       
   161             BiFunction<ObjectInput, ObjectOutput, ExecutionControl> factory) throws IOException {
       
   162         ExecutionControl[] result = new ExecutionControl[1];
       
   163         Map<String, OutputStream> augmentedStreamMap = new HashMap<>(outputStreamMap);
       
   164         ObjectOutput commandOut = new ObjectOutputStream(Util.multiplexingOutputStream("$command", output));
       
   165         for (Entry<String, InputStream> e : inputStreamMap.entrySet()) {
       
   166             InputStream  in = e.getValue();
       
   167             OutputStream inTarget = Util.multiplexingOutputStream(e.getKey(), output);
       
   168             augmentedStreamMap.put("$" + e.getKey() + "-input-requested", new OutputStream() {
       
   169                 @Override
       
   170                 public void write(int b) throws IOException {
       
   171                     //value ignored, just a trigger to read from the input
       
   172                     try {
       
   173                         int r = in.read();
       
   174                         if (r == (-1)) {
       
   175                             inTarget.write(TAG_CLOSED);
       
   176                         } else {
       
   177                             inTarget.write(new byte[] {TAG_DATA, (byte) r});
       
   178                         }
       
   179                     } catch (InterruptedIOException exc) {
       
   180                         try {
       
   181                             result[0].stop();
       
   182                         } catch (ExecutionControlException ex) {
       
   183                             debug(ex, "$" + e.getKey() + "-input-requested.write");
       
   184                         }
       
   185                     } catch (IOException exc) {
       
   186                         byte[] message = exc.getMessage().getBytes("UTF-8");
       
   187                         inTarget.write(TAG_EXCEPTION);
       
   188                         inTarget.write((message.length >>  0) & 0xFF);
       
   189                         inTarget.write((message.length >>  8) & 0xFF);
       
   190                         inTarget.write((message.length >> 16) & 0xFF);
       
   191                         inTarget.write((message.length >> 24) & 0xFF);
       
   192                         inTarget.write(message);
       
   193                     }
       
   194                 }
       
   195             });
       
   196         }
       
   197         PipeInputStream commandIn = new PipeInputStream();
       
   198         OutputStream commandInTarget = commandIn.createOutput();
       
   199         augmentedStreamMap.put("$command", commandInTarget);
       
   200         new DemultiplexInput(input, augmentedStreamMap, Arrays.asList(commandInTarget)).start();
       
   201         return result[0] = factory.apply(new ObjectInputStream(commandIn), commandOut);
       
   202     }
       
   203 
       
   204     /**
       
   205      * Monitor the JDI event stream for {@link com.sun.jdi.event.VMDeathEvent}
       
   206      * and {@link com.sun.jdi.event.VMDisconnectEvent}. If encountered, invokes
       
   207      * {@code unbiddenExitHandler}.
       
   208      *
       
   209      * @param vm the virtual machine to check
       
   210      * @param unbiddenExitHandler the handler, which will accept the exit
       
   211      * information
       
   212      */
       
   213     public static void detectJdiExitEvent(VirtualMachine vm, Consumer<String> unbiddenExitHandler) {
       
   214         if (vm.canBeModified()) {
       
   215             new JdiEventHandler(vm, unbiddenExitHandler).start();
       
   216         }
       
   217     }
       
   218 
       
   219     /**
       
   220      * Log a serious unexpected internal exception.
       
   221      *
       
   222      * @param ex the exception
       
   223      * @param where a description of the context of the exception
       
   224      */
       
   225     private static void debug(Throwable ex, String where) {
       
   226         // Reserved for future logging
       
   227     }
       
   228 }