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.ObjectInput;
  32 import java.io.ObjectInputStream;
  33 import java.io.ObjectOutput;
  34 import java.io.ObjectOutputStream;
  35 import java.io.OutputStream;
  36 import java.util.Arrays;
  37 import java.util.HashMap;
  38 import java.util.Map;
  39 import java.util.Map.Entry;
  40 import java.util.function.BiFunction;
  41 import java.util.function.Consumer;
  42 
  43 import com.sun.jdi.VirtualMachine;
  44 import jdk.jshell.spi.ExecutionControl;
  45 
  46 
  47 /**
  48  * Miscellaneous utility methods for setting-up implementations of
  49  * {@link ExecutionControl}. Particularly implementations with remote
  50  * execution.
  51  *
  52  * @author Jan Lahoda
  53  * @author Robert Field
  54  */
  55 public class Util {
  56 
  57     // never instanciated
  58     private Util() {}
  59 
  60     /**
  61      * Create a composite {@link ExecutionControl.Generator} instance that, when
  62      * generating, will try each specified generator until successfully creating
  63      * an {@link ExecutionControl} instance, or, if all fail, will re-throw the
  64      * first exception.
  65      *
  66      * @param gec0 the first instance to try
  67      * @param gecs the second through Nth instance to try
  68      * @return the fail-over generator
  69      */
  70     public static ExecutionControl.Generator failOverExecutionControlGenerator(
  71             ExecutionControl.Generator gec0, ExecutionControl.Generator... gecs) {
  72         return (ExecutionEnv env) -> {
  73             Throwable thrown;
  74             try {
  75                 return gec0.generate(env);
  76             } catch (Throwable ex) {
  77                 thrown = ex;
  78             }
  79             for (ExecutionControl.Generator gec : gecs) {
  80                 try {
  81                     return gec.generate(env);
  82                 } catch (Throwable ignore) {
  83                     // only care about the first, and only if they all fail
  84                 }
  85             }
  86             throw thrown;
  87         };
  88     }
  89 
  90     /**
  91      * Forward commands from the input to the specified {@link ExecutionControl}
  92      * instance, then responses back on the output.
  93      * @param ec the direct instance of {@link ExecutionControl} to process commands
  94      * @param in the command input
  95      * @param out the command response output
  96      */
  97     public static void forwardExecutionControl(ExecutionControl ec,
  98             ObjectInput in, ObjectOutput out) {
  99         new ExecutionControlForwarder(ec, in, out).commandLoop();
 100     }
 101 
 102     /**
 103      * Forward commands from the input to the specified {@link ExecutionControl}
 104      * instance, then responses back on the output.
 105      * @param ec the direct instance of {@link ExecutionControl} to process commands
 106      * @param inStream the stream from which to create the command input
 107      * @param outStream the stream that will carry any specified auxiliary channels (like
 108      *                  {@code System.out} and {@code System.err}), and the command response output.
 109      * @param outputStreamMap a map between names of additional streams to carry and setters
 110      *                        for the stream. Names starting with '$' are reserved for internal use.
 111      * @param inputStreamMap a map between names of additional streams to carry and setters
 112      *                       for the stream. Names starting with '$' are reserved for internal use.
 113      * @throws IOException if there are errors using the passed streams
 114      */
 115     public static void forwardExecutionControlAndIO(ExecutionControl ec,
 116             InputStream inStream, OutputStream outStream,
 117             Map<String, Consumer<OutputStream>> outputStreamMap,
 118             Map<String, Consumer<InputStream>> inputStreamMap) throws IOException {
 119         for (Entry<String, Consumer<OutputStream>> e : outputStreamMap.entrySet()) {
 120             e.getValue().accept(multiplexingOutputStream(e.getKey(), outStream));
 121         }
 122 
 123         ObjectOutputStream cmdOut = new ObjectOutputStream(multiplexingOutputStream("$command", outStream));
 124         PipeInputStream cmdInPipe = new PipeInputStream();
 125         Map<String, OutputStream> inputs = new HashMap<>();
 126         inputs.put("$command", cmdInPipe.createOutput());
 127         for (Entry<String, Consumer<InputStream>> e : inputStreamMap.entrySet()) {
 128             OutputStream inputSignal = multiplexingOutputStream("$" + e.getKey() + "-input-requested", outStream);
 129             PipeInputStream inputPipe = new PipeInputStream() {
 130                 @Override protected void inputNeeded() throws IOException {
 131                     inputSignal.write('1');
 132                     inputSignal.flush();
 133                 }
 134             };
 135             inputs.put(e.getKey(), inputPipe.createOutput());
 136             e.getValue().accept(inputPipe);
 137         }
 138         new DemultiplexInput(inStream, inputs, inputs.values()).start();
 139         ObjectInputStream cmdIn = new ObjectInputStream(cmdInPipe);
 140 
 141         forwardExecutionControl(ec, cmdIn, cmdOut);
 142     }
 143 
 144     static OutputStream multiplexingOutputStream(String label, OutputStream outputStream) {
 145         return new MultiplexingOutputStream(label, outputStream);
 146     }
 147 
 148     /**
 149      * Creates an ExecutionControl for given packetized input and output. The given InputStream
 150      * is de-packetized, and content forwarded to ObjectInput and given OutputStreams. The ObjectOutput
 151      * and values read from the given InputStream are packetized and sent to the given OutputStream.
 152      *
 153      * @param input the packetized input stream
 154      * @param output the packetized output stream
 155      * @param outputStreamMap a map between stream names and the output streams to forward.
 156      *                        Names starting with '$' are reserved for internal use.
 157      * @param inputStreamMap a map between stream names and the input streams to forward.
 158      *                       Names starting with '$' are reserved for internal use.
 159      * @param factory to create the ExecutionControl from ObjectInput and ObjectOutput.
 160      * @return the created ExecutionControl
 161      * @throws IOException if setting up the streams raised an exception
 162      */
 163     public static ExecutionControl remoteInputOutput(InputStream input, OutputStream output,
 164             Map<String, OutputStream> outputStreamMap, Map<String, InputStream> inputStreamMap,
 165             BiFunction<ObjectInput, ObjectOutput, ExecutionControl> factory) throws IOException {
 166         Map<String, OutputStream> augmentedStreamMap = new HashMap<>(outputStreamMap);
 167         ObjectOutput commandOut = new ObjectOutputStream(Util.multiplexingOutputStream("$command", output));
 168         for (Entry<String, InputStream> e : inputStreamMap.entrySet()) {
 169             InputStream  in = e.getValue();
 170             OutputStream inTarget = Util.multiplexingOutputStream(e.getKey(), output);
 171             augmentedStreamMap.put("$" + e.getKey() + "-input-requested", new OutputStream() {
 172                 @Override
 173                 public void write(int b) throws IOException {
 174                     //value ignored, just a trigger to read from the input
 175                     inTarget.write(in.read());
 176                 }
 177             });
 178         }
 179         PipeInputStream commandIn = new PipeInputStream();
 180         OutputStream commandInTarget = commandIn.createOutput();
 181         augmentedStreamMap.put("$command", commandInTarget);
 182         new DemultiplexInput(input, augmentedStreamMap, Arrays.asList(commandInTarget)).start();
 183         return factory.apply(new ObjectInputStream(commandIn), commandOut);
 184     }
 185 
 186     /**
 187      * Monitor the JDI event stream for {@link com.sun.jdi.event.VMDeathEvent}
 188      * and {@link com.sun.jdi.event.VMDisconnectEvent}. If encountered, invokes
 189      * {@code unbiddenExitHandler}.
 190      *
 191      * @param vm the virtual machine to check
 192      * @param unbiddenExitHandler the handler, which will accept the exit
 193      * information
 194      */
 195     public static void detectJDIExitEvent(VirtualMachine vm, Consumer<String> unbiddenExitHandler) {
 196         if (vm.canBeModified()) {
 197             new JDIEventHandler(vm, unbiddenExitHandler).start();
 198         }
 199     }
 200 
 201 }