langtools/src/jdk.jshell/share/classes/jdk/jshell/execution/Util.java
author iris
Thu, 21 Jul 2016 07:43:17 -0700
changeset 39809 74b7aea686d8
parent 39807 ba0ff343d241
child 40767 c7908e8c786b
permissions -rw-r--r--
8161236: Runtime.Version.{compareTo, equals}IgnoreOpt should be renamed Reviewed-by: alanb, rfield, ksrini

/*
 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package jdk.jshell.execution;

import jdk.jshell.spi.ExecutionEnv;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import com.sun.jdi.VirtualMachine;
import jdk.jshell.spi.ExecutionControl;


/**
 * Miscellaneous utility methods for setting-up implementations of
 * {@link ExecutionControl}. Particularly implementations with remote
 * execution.
 *
 * @author Jan Lahoda
 * @author Robert Field
 */
public class Util {

    // never instanciated
    private Util() {}

    /**
     * Create a composite {@link ExecutionControl.Generator} instance that, when
     * generating, will try each specified generator until successfully creating
     * an {@link ExecutionControl} instance, or, if all fail, will re-throw the
     * first exception.
     *
     * @param gec0 the first instance to try
     * @param gecs the second through Nth instance to try
     * @return the fail-over generator
     */
    public static ExecutionControl.Generator failOverExecutionControlGenerator(
            ExecutionControl.Generator gec0, ExecutionControl.Generator... gecs) {
        return (ExecutionEnv env) -> {
            Throwable thrown;
            try {
                return gec0.generate(env);
            } catch (Throwable ex) {
                thrown = ex;
            }
            for (ExecutionControl.Generator gec : gecs) {
                try {
                    return gec.generate(env);
                } catch (Throwable ignore) {
                    // only care about the first, and only if they all fail
                }
            }
            throw thrown;
        };
    }

    /**
     * Forward commands from the input to the specified {@link ExecutionControl}
     * instance, then responses back on the output.
     * @param ec the direct instance of {@link ExecutionControl} to process commands
     * @param in the command input
     * @param out the command response output
     */
    public static void forwardExecutionControl(ExecutionControl ec,
            ObjectInput in, ObjectOutput out) {
        new ExecutionControlForwarder(ec, in, out).commandLoop();
    }

    /**
     * Forward commands from the input to the specified {@link ExecutionControl}
     * instance, then responses back on the output.
     * @param ec the direct instance of {@link ExecutionControl} to process commands
     * @param inStream the stream from which to create the command input
     * @param outStream the stream that will carry {@code System.out},
     * {@code System.err}, any specified auxiliary channels, and the
     * command response output.
     * @param streamMap a map between names of additional streams to carry and setters
     * for the stream
     * @throws IOException if there are errors using the passed streams
     */
    public static void forwardExecutionControlAndIO(ExecutionControl ec,
            InputStream inStream, OutputStream outStream,
            Map<String, Consumer<OutputStream>> streamMap) throws IOException {
        ObjectInputStream cmdIn = new ObjectInputStream(inStream);
        for (Entry<String, Consumer<OutputStream>> e : streamMap.entrySet()) {
            e.getValue().accept(multiplexingOutputStream(e.getKey(), outStream));
        }
        ObjectOutputStream cmdOut = new ObjectOutputStream(multiplexingOutputStream("command", outStream));
        forwardExecutionControl(ec, cmdIn, cmdOut);
    }

    static OutputStream multiplexingOutputStream(String label, OutputStream outputStream) {
        return new MultiplexingOutputStream(label, outputStream);
    }

    /**
     * Reads from an InputStream which has been packetized and write its contents
     * to the out and err OutputStreams; Copies the command stream.
     * @param input the packetized input stream
     * @param streamMap a map between stream names and the output streams to forward
     * @return the command stream
     * @throws IOException if setting up the streams raised an exception
     */
    public static ObjectInput remoteInput(InputStream input,
            Map<String, OutputStream> streamMap) throws IOException {
        PipeInputStream commandIn = new PipeInputStream();
        new DemultiplexInput(input, commandIn, streamMap).start();
        return new ObjectInputStream(commandIn);
    }

    /**
     * Monitor the JDI event stream for {@link com.sun.jdi.event.VMDeathEvent}
     * and {@link com.sun.jdi.event.VMDisconnectEvent}. If encountered, invokes
     * {@code unbiddenExitHandler}.
     *
     * @param vm the virtual machine to check
     * @param unbiddenExitHandler the handler, which will accept the exit
     * information
     */
    public static void detectJDIExitEvent(VirtualMachine vm, Consumer<String> unbiddenExitHandler) {
        if (vm.canBeModified()) {
            new JDIEventHandler(vm, unbiddenExitHandler).start();
        }
    }

    /**
     * Creates a Thread that will ship all input to the remote agent.
     *
     * @param inputStream the user input
     * @param outStream the input to the remote agent
     * @param handler a failure handler
     */
    public static void forwardInputToRemote(final InputStream inputStream,
            final OutputStream outStream, final Consumer<Exception> handler) {
        Thread thr = new Thread("input reader") {
            @Override
            public void run() {
                try {
                    byte[] buf = new byte[256];
                    int cnt;
                    while ((cnt = inputStream.read(buf)) != -1) {
                        outStream.write(buf, 0, cnt);
                        outStream.flush();
                    }
                } catch (Exception ex) {
                    handler.accept(ex);
                }
            }
        };
        thr.setPriority(Thread.MAX_PRIORITY - 1);
        thr.start();
    }

}