Util.java revision 3846:605b0823d19b
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 */ 25package jdk.jshell.execution; 26 27import jdk.jshell.spi.ExecutionEnv; 28 29import java.io.IOException; 30import java.io.InputStream; 31import java.io.InterruptedIOException; 32import java.io.ObjectInput; 33import java.io.ObjectInputStream; 34import java.io.ObjectOutput; 35import java.io.ObjectOutputStream; 36import java.io.OutputStream; 37import java.util.Arrays; 38import java.util.HashMap; 39import java.util.Map; 40import java.util.Map.Entry; 41import java.util.function.BiFunction; 42import java.util.function.Consumer; 43 44import com.sun.jdi.VirtualMachine; 45import jdk.jshell.spi.ExecutionControl; 46import 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 */ 57public class Util { 58 59 private static final int TAG_DATA = 0; 60 private static final int TAG_CLOSED = 1; 61 private static final int TAG_EXCEPTION = 2; 62 63 // never instanciated 64 private Util() {} 65 66 /** 67 * Forward commands from the input to the specified {@link ExecutionControl} 68 * instance, then responses back on the output. 69 * @param ec the direct instance of {@link ExecutionControl} to process commands 70 * @param in the command input 71 * @param out the command response output 72 */ 73 public static void forwardExecutionControl(ExecutionControl ec, 74 ObjectInput in, ObjectOutput out) { 75 new ExecutionControlForwarder(ec, in, out).commandLoop(); 76 } 77 78 /** 79 * Forward commands from the input to the specified {@link ExecutionControl} 80 * instance, then responses back on the output. 81 * @param ec the direct instance of {@link ExecutionControl} to process commands 82 * @param inStream the stream from which to create the command input 83 * @param outStream the stream that will carry any specified auxiliary channels (like 84 * {@code System.out} and {@code System.err}), and the command response output. 85 * @param outputStreamMap a map between names of additional streams to carry and setters 86 * for the stream. Names starting with '$' are reserved for internal use. 87 * @param inputStreamMap a map between names of additional streams to carry and setters 88 * for the stream. Names starting with '$' are reserved for internal use. 89 * @throws IOException if there are errors using the passed streams 90 */ 91 public static void forwardExecutionControlAndIO(ExecutionControl ec, 92 InputStream inStream, OutputStream outStream, 93 Map<String, Consumer<OutputStream>> outputStreamMap, 94 Map<String, Consumer<InputStream>> inputStreamMap) throws IOException { 95 for (Entry<String, Consumer<OutputStream>> e : outputStreamMap.entrySet()) { 96 e.getValue().accept(multiplexingOutputStream(e.getKey(), outStream)); 97 } 98 99 ObjectOutputStream cmdOut = new ObjectOutputStream(multiplexingOutputStream("$command", outStream)); 100 PipeInputStream cmdInPipe = new PipeInputStream(); 101 Map<String, OutputStream> inputs = new HashMap<>(); 102 inputs.put("$command", cmdInPipe.createOutput()); 103 for (Entry<String, Consumer<InputStream>> e : inputStreamMap.entrySet()) { 104 OutputStream inputSignal = multiplexingOutputStream("$" + e.getKey() + "-input-requested", outStream); 105 PipeInputStream inputPipe = new PipeInputStream() { 106 @Override protected void inputNeeded() throws IOException { 107 inputSignal.write('1'); 108 inputSignal.flush(); 109 } 110 @Override 111 public synchronized int read() throws IOException { 112 int tag = super.read(); 113 switch (tag) { 114 case TAG_DATA: return super.read(); 115 case TAG_CLOSED: close(); return -1; 116 case TAG_EXCEPTION: 117 int len = (super.read() << 0) + (super.read() << 8) + (super.read() << 16) + (super.read() << 24); 118 byte[] message = new byte[len]; 119 for (int i = 0; i < len; i++) { 120 message[i] = (byte) super.read(); 121 } 122 throw new IOException(new String(message, "UTF-8")); 123 case -1: 124 return -1; 125 default: 126 throw new IOException("Internal error: unrecognized message tag: " + tag); 127 } 128 } 129 }; 130 inputs.put(e.getKey(), inputPipe.createOutput()); 131 e.getValue().accept(inputPipe); 132 } 133 new DemultiplexInput(inStream, inputs, inputs.values()).start(); 134 ObjectInputStream cmdIn = new ObjectInputStream(cmdInPipe); 135 136 forwardExecutionControl(ec, cmdIn, cmdOut); 137 } 138 139 static OutputStream multiplexingOutputStream(String label, OutputStream outputStream) { 140 return new MultiplexingOutputStream(label, outputStream); 141 } 142 143 /** 144 * Creates an ExecutionControl for given packetized input and output. The given InputStream 145 * is de-packetized, and content forwarded to ObjectInput and given OutputStreams. The ObjectOutput 146 * and values read from the given InputStream are packetized and sent to the given OutputStream. 147 * 148 * @param input the packetized input stream 149 * @param output the packetized output stream 150 * @param outputStreamMap a map between stream names and the output streams to forward. 151 * Names starting with '$' are reserved for internal use. 152 * @param inputStreamMap a map between stream names and the input streams to forward. 153 * Names starting with '$' are reserved for internal use. 154 * @param factory to create the ExecutionControl from ObjectInput and ObjectOutput. 155 * @return the created ExecutionControl 156 * @throws IOException if setting up the streams raised an exception 157 */ 158 public static ExecutionControl remoteInputOutput(InputStream input, OutputStream output, 159 Map<String, OutputStream> outputStreamMap, Map<String, InputStream> inputStreamMap, 160 BiFunction<ObjectInput, ObjectOutput, ExecutionControl> factory) throws IOException { 161 ExecutionControl[] result = new ExecutionControl[1]; 162 Map<String, OutputStream> augmentedStreamMap = new HashMap<>(outputStreamMap); 163 ObjectOutput commandOut = new ObjectOutputStream(Util.multiplexingOutputStream("$command", output)); 164 for (Entry<String, InputStream> e : inputStreamMap.entrySet()) { 165 InputStream in = e.getValue(); 166 OutputStream inTarget = Util.multiplexingOutputStream(e.getKey(), output); 167 augmentedStreamMap.put("$" + e.getKey() + "-input-requested", new OutputStream() { 168 @Override 169 public void write(int b) throws IOException { 170 //value ignored, just a trigger to read from the input 171 try { 172 int r = in.read(); 173 if (r == (-1)) { 174 inTarget.write(TAG_CLOSED); 175 } else { 176 inTarget.write(new byte[] {TAG_DATA, (byte) r}); 177 } 178 } catch (InterruptedIOException exc) { 179 try { 180 result[0].stop(); 181 } catch (ExecutionControlException ex) { 182 debug(ex, "$" + e.getKey() + "-input-requested.write"); 183 } 184 } catch (IOException exc) { 185 byte[] message = exc.getMessage().getBytes("UTF-8"); 186 inTarget.write(TAG_EXCEPTION); 187 inTarget.write((message.length >> 0) & 0xFF); 188 inTarget.write((message.length >> 8) & 0xFF); 189 inTarget.write((message.length >> 16) & 0xFF); 190 inTarget.write((message.length >> 24) & 0xFF); 191 inTarget.write(message); 192 } 193 } 194 }); 195 } 196 PipeInputStream commandIn = new PipeInputStream(); 197 OutputStream commandInTarget = commandIn.createOutput(); 198 augmentedStreamMap.put("$command", commandInTarget); 199 new DemultiplexInput(input, augmentedStreamMap, Arrays.asList(commandInTarget)).start(); 200 return result[0] = factory.apply(new ObjectInputStream(commandIn), commandOut); 201 } 202 203 /** 204 * Monitor the JDI event stream for {@link com.sun.jdi.event.VMDeathEvent} 205 * and {@link com.sun.jdi.event.VMDisconnectEvent}. If encountered, invokes 206 * {@code unbiddenExitHandler}. 207 * 208 * @param vm the virtual machine to check 209 * @param unbiddenExitHandler the handler, which will accept the exit 210 * information 211 */ 212 public static void detectJdiExitEvent(VirtualMachine vm, Consumer<String> unbiddenExitHandler) { 213 if (vm.canBeModified()) { 214 new JdiEventHandler(vm, unbiddenExitHandler).start(); 215 } 216 } 217 218 /** 219 * Log a serious unexpected internal exception. 220 * 221 * @param ex the exception 222 * @param where a description of the context of the exception 223 */ 224 private static void debug(Throwable ex, String where) { 225 // Reserved for future logging 226 } 227} 228