com.github.caofangkun.bazelipse.command.Command.java Source code

Java tutorial

Introduction

Here is the source code for com.github.caofangkun.bazelipse.command.Command.java

Source

// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.github.caofangkun.bazelipse.command;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.function.Function;

import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;

/**
 * A utility class to spawn a command and parse its output. It allow to filter the output,
 * redirecting part of it to the console and getting the rest in a list of string.
 *
 * <p>
 * This class can only be initialized using a builder created with the {@link #builder()} method.
 */
final class Command {

    private final File directory;
    private final ImmutableList<String> args;
    private final SelectOutputStream stdout;
    private final SelectOutputStream stderr;
    private boolean executed = false;

    private Command(String consoleName, File directory, ImmutableList<String> args,
            Function<String, String> stdoutSelector, Function<String, String> stderrSelector, OutputStream stdout,
            OutputStream stderr) throws IOException {
        this.directory = directory;
        this.args = args;
        if (consoleName != null) {
            MessageConsole console = findConsole(consoleName);
            MessageConsoleStream stream = console.newMessageStream();
            stream.setActivateOnWrite(true);
            stream.write(
                    "*** Running " + String.join("", args.toString()) + " from " + directory.toString() + " ***\n");
            if (stdout == null) {
                stdout = console.newMessageStream();
            }
            if (stderr == null) {
                stderr = getErrorStream(console);
            }
        }
        this.stderr = new SelectOutputStream(stderr, stderrSelector);
        this.stdout = new SelectOutputStream(stdout, stdoutSelector);
    }

    /**
     * Executes the command represented by this instance, and return the exit code of the command.
     * This method should not be called twice on the same object.
     */
    public int run() throws IOException, InterruptedException {
        Preconditions.checkState(!executed);
        executed = true;
        ProcessBuilder builder = new ProcessBuilder(args);
        builder.directory(directory);
        Process process = builder.start();
        copyStream(process.getErrorStream(), stderr);
        // seriously? That's stdout, why is it called getInputStream???
        copyStream(process.getInputStream(), stdout);
        int r = process.waitFor();
        synchronized (stderr) {
            stderr.close();
        }
        synchronized (stdout) {
            stdout.close();
        }
        return r;
    }

    // Taken from the eclipse website, find a console
    private static MessageConsole findConsole(String name) {
        ConsolePlugin plugin = ConsolePlugin.getDefault();
        IConsoleManager conMan = plugin.getConsoleManager();
        IConsole[] existing = conMan.getConsoles();
        for (int i = 0; i < existing.length; i++) {
            if (name.equals(existing[i].getName())) {
                return (MessageConsole) existing[i];
            }
        }
        // no console found, so create a new one
        MessageConsole myConsole = new MessageConsole(name, null);
        conMan.addConsoles(new IConsole[] { myConsole });
        return myConsole;
    }

    // Get the error stream for the given console (a stream that print in red).
    private static MessageConsoleStream getErrorStream(MessageConsole console) {
        final MessageConsoleStream errorStream = console.newMessageStream();
        Display display = Display.getCurrent();
        if (display == null) {
            display = Display.getDefault();
        }
        display.asyncExec(() -> errorStream.setColor(new Color(null, 255, 0, 0)));
        return errorStream;
    }

    // Launch a thread to copy all data from inputStream to outputStream
    private static void copyStream(InputStream inputStream, OutputStream outputStream) {
        if (outputStream != null)
            new Thread(new Runnable() {
                @Override
                public void run() {
                    byte[] buffer = new byte[4096];
                    int read;
                    try {
                        while ((read = inputStream.read(buffer)) > 0) {
                            synchronized (outputStream) {
                                outputStream.write(buffer, 0, read);
                            }
                        }
                    } catch (IOException ex) {
                        // we simply terminate the thread on exceptions
                    }
                }
            }).start();
    }

    /**
     * Returns the list of lines selected from the standard error stream. Lines printed to the
     * standard error stream by the executed command can be filtered to be added to that list.
     *
     * @see {@link Builder#setStderrLineSelector(Function)}
     */
    ImmutableList<String> getSelectedErrorLines() {
        return stderr.getLines();
    }

    /**
     * Returns the list of lines selected from the standard output stream. Lines printed to the
     * standard output stream by the executed command can be filtered to be added to that list.
     *
     * @see {@link Builder#setStdoutLineSelector(Function)}
     */
    ImmutableList<String> getSelectedOutputLines() {
        return stdout.getLines();
    }

    /**
     * A builder class to generate a Command object.
     */
    static class Builder {

        private String consoleName = null;
        private File directory;
        private ImmutableList.Builder<String> args = ImmutableList.builder();
        private OutputStream stdout = null;
        private OutputStream stderr = null;
        private Function<String, String> stdoutSelector;
        private Function<String, String> stderrSelector;

        private Builder() {
            // Default to the current working directory
            this.directory = new File(System.getProperty("user.dir"));
        }

        /**
         * Set the console name.
         *
         * <p>
         * The console name is used to print result of the program. Only lines not filtered by
         * {@link #setStderrLineSelector(Function)} and {@link #setStdoutLineSelector(Function)} are
         * printed to the console. If {@link #setStandardError(OutputStream)} or
         * {@link #setStandardOutput(OutputStream)} have been used with a non null value, then they
         * intercept all output from being printed to the console.
         *
         * <p>
         * If name is null, no output is written to any console.
         */
        public Builder setConsoleName(String name) {
            this.consoleName = name;
            return this;
        }

        /**
         * Set the working directory for the program, it is set to the current working directory of the
         * current java process by default.
         */
        public Builder setDirectory(File directory) {
            this.directory = directory;
            return this;
        }

        /**
         * Set an {@link OutputStream} to receive non selected lines from the standard output stream of
         * the program in lieu of the console. If a selector has been set with
         * {@link #setStdoutLineSelector(Function)}, only the lines not selected (for which the selector
         * returns null) will be printed to the {@link OutputStream}.
         */
        public Builder setStandardOutput(OutputStream stdout) {
            this.stdout = stdout;
            return this;
        }

        /**
         * Set an {@link OutputStream} to receive non selected lines from the standard error stream of
         * the program in lieu of the console. If a selector has been set with
         * {@link #setStderrLineSelector(Function)}, only the lines not selected (for which the selector
         * returns null) will be printed to the {@link OutputStream}.
         */
        public Builder setStandardError(OutputStream stderr) {
            this.stderr = stderr;
            return this;
        }

        /**
         * Add arguments to the command line. The first argument to be added to the builder is the
         * program name.
         */
        public Builder addArguments(String... args) {
            this.args.add(args);
            return this;
        }

        /**
         * Add a list of arguments to the command line. The first argument to be added to the builder is
         * the program name.
         */
        public Builder addArguments(Iterable<String> args) {
            this.args.addAll(args);
            return this;
        }

        /**
         * Set a selector to accumulate lines that are selected from the standard output stream.
         *
         * <p>
         * The selector is passed all lines that are printed to the standard output. It can either
         * returns null to say that the line should be passed to the console or to a non null value that
         * will be stored. All values that have been selected (for which the selector returns a non-null
         * value) will be stored in a list accessible through {@link Command#getSelectedOutputLines()}.
         * The selected lines will not be printed to the console.
         */
        public Builder setStdoutLineSelector(Function<String, String> selector) {
            this.stdoutSelector = selector;
            return this;
        }

        /**
         * Set a selector to accumulate lines that are selected from the standard error stream.
         *
         * <p>
         * The selector is passed all lines that are printed to the standard error. It can either
         * returns null to say that the line should be passed to the console or to a non null value that
         * will be stored. All values that have been selected (for which the selector returns a non-null
         * value) will be stored in a list accessible through {@link Command#getSelectedErrorLines()}.
         * The selected lines will not be printed to the console.
         */
        public Builder setStderrLineSelector(Function<String, String> selector) {
            this.stderrSelector = selector;
            return this;
        }

        /**
         * Build a Command object.
         */
        public Command build() throws IOException {
            Preconditions.checkNotNull(directory);
            return new Command(consoleName, directory, args.build(), stdoutSelector, stderrSelector, stdout,
                    stderr);
        }
    }

    /**
     * Returns a {@link Builder} object to use to create a {@link Command} object.
     */
    static Builder builder() {
        return new Builder();
    }
}