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 import java.io.IOException;
  29 import java.io.InputStream;
  30 import java.io.ObjectInput;
  31 import java.io.ObjectInputStream;
  32 import java.io.ObjectOutput;
  33 import java.io.ObjectOutputStream;
  34 import java.io.OutputStream;
  35 import java.io.PrintStream;
  36 import java.net.Socket;
  37 import java.util.AbstractMap.SimpleEntry;
  38 import java.util.Collections;
  39 import java.util.HashMap;
  40 import java.util.Map;
  41 import java.util.Map.Entry;
  42 import java.util.function.Consumer;
  43 import java.util.stream.Collectors;
  44 import java.util.stream.Stream;
  45 import com.sun.jdi.VirtualMachine;
  46 import jdk.jshell.spi.ExecutionControl;
  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  */
  57 public class Util {
  58 
  59     // never instanciated
  60     private Util() {}
  61 
  62     /**
  63      * Create a composite {@link ExecutionControl.Generator} instance that, when
  64      * generating, will try each specified generator until successfully creating
  65      * an {@link ExecutionControl} instance, or, if all fail, will re-throw the
  66      * first exception.
  67      *
  68      * @param gec0 the first instance to try
  69      * @param gecs the second through Nth instance to try
  70      * @return the fail-over generator
  71      */
  72     public static ExecutionControl.Generator failOverExecutionControlGenerator(
  73             ExecutionControl.Generator gec0, ExecutionControl.Generator... gecs) {
  74         return (ExecutionEnv env) -> {
  75             Throwable thrown;
  76             try {
  77                 return gec0.generate(env);
  78             } catch (Throwable ex) {
  79                 thrown = ex;
  80             }
  81             for (ExecutionControl.Generator gec : gecs) {
  82                 try {
  83                     return gec.generate(env);
  84                 } catch (Throwable ignore) {
  85                     // only care about the first, and only if they all fail
  86                 }
  87             }
  88             throw thrown;
  89         };
  90     }
  91 
  92     /**
  93      * Forward commands from the input to the specified {@link ExecutionControl}
  94      * instance, then responses back on the output.
  95      * @param ec the direct instance of {@link ExecutionControl} to process commands
  96      * @param in the command input
  97      * @param out the command response output
  98      */
  99     public static void forwardExecutionControl(ExecutionControl ec,
 100             ObjectInput in, ObjectOutput out) {
 101         new ExecutionControlForwarder(ec, in, out).commandLoop();
 102     }
 103 
 104     /**
 105      * Forward commands from the input to the specified {@link ExecutionControl}
 106      * instance, then responses back on the output.
 107      * @param ec the direct instance of {@link ExecutionControl} to process commands
 108      * @param inStream the stream from which to create the command input
 109      * @param outStream the stream that will carry {@code System.out},
 110      * {@code System.err}, any specified auxiliary channels, and the
 111      * command response output.
 112      * @param streamMap a map between names of additional streams to carry and setters
 113      * for the stream
 114      * @throws IOException if there are errors using the passed streams
 115      */
 116     public static void forwardExecutionControlAndIO(ExecutionControl ec,
 117             InputStream inStream, OutputStream outStream,
 118             Map<String, Consumer<OutputStream>> streamMap) throws IOException {
 119         ObjectInputStream cmdIn = new ObjectInputStream(inStream);
 120         for (Entry<String, Consumer<OutputStream>> e : streamMap.entrySet()) {
 121             e.getValue().accept(multiplexingOutputStream(e.getKey(), outStream));
 122         }
 123         ObjectOutputStream cmdOut = new ObjectOutputStream(multiplexingOutputStream("command", outStream));
 124         forwardExecutionControl(ec, cmdIn, cmdOut);
 125     }
 126 
 127     static OutputStream multiplexingOutputStream(String label, OutputStream outputStream) {
 128         return new MultiplexingOutputStream(label, outputStream);
 129     }
 130 
 131     /**
 132      * Reads from an InputStream which has been packetized and write its contents
 133      * to the out and err OutputStreams; Copies the command stream.
 134      * @param input the packetized input stream
 135      * @param streamMap a map between stream names and the output streams to forward
 136      * @return the command stream
 137      * @throws IOException if setting up the streams raised an exception
 138      */
 139     public static ObjectInput remoteInput(InputStream input,
 140             Map<String, OutputStream> streamMap) throws IOException {
 141         PipeInputStream commandIn = new PipeInputStream();
 142         new DemultiplexInput(input, commandIn, streamMap).start();
 143         return new ObjectInputStream(commandIn);
 144     }
 145 
 146     /**
 147      * Monitor the JDI event stream for {@link com.sun.jdi.event.VMDeathEvent}
 148      * and {@link com.sun.jdi.event.VMDisconnectEvent}. If encountered, invokes
 149      * {@code unbiddenExitHandler}.
 150      *
 151      * @param vm the virtual machine to check
 152      * @param unbiddenExitHandler the handler, which will accept the exit
 153      * information
 154      */
 155     public static void detectJDIExitEvent(VirtualMachine vm, Consumer<String> unbiddenExitHandler) {
 156         if (vm.canBeModified()) {
 157             new JDIEventHandler(vm, unbiddenExitHandler).start();
 158         }
 159     }
 160 
 161     /**
 162      * Creates a Thread that will ship all input to the remote agent.
 163      *
 164      * @param inputStream the user input
 165      * @param outStream the input to the remote agent
 166      * @param handler a failure handler
 167      */
 168     public static void forwardInputToRemote(final InputStream inputStream,
 169             final OutputStream outStream, final Consumer<Exception> handler) {
 170         Thread thr = new Thread("input reader") {
 171             @Override
 172             public void run() {
 173                 try {
 174                     byte[] buf = new byte[256];
 175                     int cnt;
 176                     while ((cnt = inputStream.read(buf)) != -1) {
 177                         outStream.write(buf, 0, cnt);
 178                         outStream.flush();
 179                     }
 180                 } catch (Exception ex) {
 181                     handler.accept(ex);
 182                 }
 183             }
 184         };
 185         thr.setPriority(Thread.MAX_PRIORITY - 1);
 186         thr.start();
 187     }
 188 
 189 }