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
26package java.util.spi;
27
28import java.io.PrintStream;
29import java.io.PrintWriter;
30import java.security.AccessController;
31import java.security.PrivilegedAction;
32import java.util.Objects;
33import java.util.Optional;
34import java.util.ServiceLoader;
35import java.util.stream.StreamSupport;
36
37/**
38 * An interface for command-line tools to provide a way to
39 * be invoked without necessarily starting a new VM.
40 *
41 * <p>Tool providers are normally located using the service-provider
42 * loading facility defined by {@link ServiceLoader}.
43 * Each provider must provide a name, and a method to run
44 * an instance of the corresponding tool. When a tool is run,
45 * it will be provided with an array of string arguments, and a
46 * pair of streams: one for normal (or expected) output and the other
47 * for reporting any errors that may occur.
48 * The interpretation of the string arguments will normally be defined by
49 * each individual tool provider, but will generally correspond to the
50 * arguments that could be provided to the tool when invoking the tool
51 * from the command line.
52 *
53 * @since 9
54 */
55public interface ToolProvider {
56    /**
57     * Returns the name of this tool provider.
58     *
59     * @apiNote It is recommended that the name be the same as would be
60     * used on the command line: for example, "javac", "jar", "jlink".
61     *
62     * @return the name of this tool provider
63     */
64    String name();
65
66    /**
67     * Runs an instance of the tool, returning zero for a successful run.
68     * Any non-zero return value indicates a tool-specific error during the
69     * execution.
70     *
71     * Two streams should be provided, for "expected" output, and for any
72     * error messages. If it is not necessary to distinguish the output,
73     * the same stream may be used for both.
74     *
75     * @apiNote The interpretation of the arguments will be specific to
76     * each tool.
77     *
78     * @param out a stream to which "expected" output should be written
79     *
80     * @param err a stream to which any error messages should be written
81     *
82     * @param args the command-line arguments for the tool
83     *
84     * @return the result of executing the tool.
85     *         A return value of 0 means the tool did not encounter any errors;
86     *         any other value indicates that at least one error occurred
87     *         during execution.
88     *
89     * @throws NullPointerException if any of the arguments are {@code null},
90     *         or if there are any {@code null} values in the {@code args}
91     *         array
92     */
93    int run(PrintWriter out, PrintWriter err, String... args);
94
95    /**
96     * Runs an instance of the tool, returning zero for a successful run.
97     * Any non-zero return value indicates a tool-specific error during the
98     * execution.
99     *
100     * Two streams should be provided, for "expected" output, and for any
101     * error messages. If it is not necessary to distinguish the output,
102     * the same stream may be used for both.
103     *
104     * @apiNote The interpretation of the arguments will be specific to
105     * each tool.
106     *
107     * @implNote This implementation wraps the {@code out} and {@code err}
108     * streams within {@link PrintWriter}s, and then calls
109     * {@link #run(PrintWriter, PrintWriter, String[])}.
110     *
111     * @param out a stream to which "expected" output should be written
112     *
113     * @param err a stream to which any error messages should be written
114     *
115     * @param args the command-line arguments for the tool
116     *
117     * @return the result of executing the tool.
118     *         A return value of 0 means the tool did not encounter any errors;
119     *         any other value indicates that at least one error occurred
120     *         during execution.
121     *
122     * @throws NullPointerException if any of the arguments are {@code null},
123     *         or if there are any {@code null} values in the {@code args}
124     *         array
125     */
126    default int run(PrintStream out, PrintStream err, String... args) {
127        Objects.requireNonNull(out);
128        Objects.requireNonNull(err);
129        for (String arg : args) {
130            Objects.requireNonNull(args);
131        }
132
133        PrintWriter outWriter = new PrintWriter(out);
134        PrintWriter errWriter = new PrintWriter(err);
135        try {
136            try {
137                return run(outWriter, errWriter, args);
138            } finally {
139                outWriter.flush();
140            }
141        } finally {
142            errWriter.flush();
143        }
144    }
145
146    /**
147     * Returns the first instance of a {@code ToolProvider} with the given name,
148     * as loaded by {@link ServiceLoader} using the system class loader.
149     *
150     * @param name the name of the desired tool provider
151     *
152     * @return an {@code Optional<ToolProvider>} of the first instance found
153     *
154     * @throws NullPointerException if {@code name} is {@code null}
155     */
156    static Optional<ToolProvider> findFirst(String name) {
157        Objects.requireNonNull(name);
158        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
159        return AccessController.doPrivileged(
160            (PrivilegedAction<Optional<ToolProvider>>) () -> {
161                ServiceLoader<ToolProvider> sl =
162                    ServiceLoader.load(ToolProvider.class, systemClassLoader);
163                return StreamSupport.stream(sl.spliterator(), false)
164                    .filter(p -> p.name().equals(name))
165                    .findFirst();
166            });
167    }
168}
169
170