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