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 }