8159855: Create an SPI for tools

Reviewed-by: mchung
This commit is contained in:
Jonathan Gibbons 2016-10-07 16:49:31 -07:00
parent 2d52d60a1e
commit 0f9a011475
3 changed files with 271 additions and 0 deletions

View File

@ -0,0 +1,165 @@
/*
* 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 java.util.spi;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.StreamSupport;
/**
* An interface for command-line tools to provide a way to
* be invoked without necessarily starting a new VM.
*
* <p>Tool providers are normally located using the service-provider
* loading facility defined by {@link ServiceLoader}.
* Each provider must provide a name, and a method to run
* an instance of the corresponding tool. When a tool is run,
* it will be provided with an array of string arguments, and a
* pair of streams: one for normal (or expected) output and the other
* for reporting any errors that may occur.
* The interpretation of the string arguments will normally be defined by
* each individual tool provider, but will generally correspond to the
* arguments that could be provided to the tool when invoking the tool
* from the command line.
*
* @since 9
*/
public interface ToolProvider {
/**
* Returns the name of this tool provider.
*
* @apiNote It is recommended that the name be the same as would be used on
* the command line: for example, "javac", "jar", "jlink".
*
* @return the name of this tool provider
*/
String name();
/**
* Runs an instance of the tool, returning zero for a successful run.
* Any non-zero return value indicates a tool-specific error during the
* execution.
* Two streams should be provided, for "expected" output, and for any
* error messages. If it is not necessary to distinguish the output,
* the same stream may be used for both.
*
* @apiNote The interpretation of the arguments will be specific to
* each tool.
*
* @param out a stream to which "expected" output should be written
*
* @param err a stream to which any error messages should be written
*
* @param args the command-line arguments for the tool
*
* @return the result of executing the tool.
* A return value of 0 means the tool did not encounter any errors;
* any other value indicates that at least one error occurred during
* execution.
*
* @throws NullPointerException if any of the arguments are {@code null},
* or if there are any {@code null} values in the {@code args} array
*/
int run(PrintWriter out, PrintWriter err, String... args);
/**
* Runs an instance of the tool, returning zero for a successful run.
* Any non-zero return value indicates a tool-specific error during the
* execution.
* Two streams should be provided, for "expected" output, and for any
* error messages. If it is not necessary to distinguish the output,
* the same stream may be used for both.
*
* @apiNote The interpretation of the arguments will be specific to
* each tool.
*
* @implNote This implementation wraps the {@code out} and {@code err}
* streams within {@link PrintWriter}s, and then calls
* {@link run(PrintWriter, PrintWriter, String[])}.
*
* @param out a stream to which "expected" output should be written
*
* @param err a stream to which any error messages should be written
*
* @param args the command-line arguments for the tool
*
* @return the result of executing the tool.
* A return value of 0 means the tool did not encounter any errors;
* any other value indicates that at least one error occurred during
* execution.
*
* @throws NullPointerException if any of the arguments are {@code null},
* or if there are any {@code null} values in the {@code args} array
*/
default int run(PrintStream out, PrintStream err, String... args) {
Objects.requireNonNull(out);
Objects.requireNonNull(err);
for (String arg : args) {
Objects.requireNonNull(args);
}
PrintWriter outWriter = new PrintWriter(out);
PrintWriter errWriter = new PrintWriter(err);
try {
try {
return run(outWriter, errWriter, args);
} finally {
outWriter.flush();
}
} finally {
errWriter.flush();
}
}
/**
* Returns the first instance of a {@code ToolProvider} with the given name,
* as loaded by {@link ServiceLoader} using the system class loader.
*
* @param name the name of the desired tool provider
*
* @return an {@code Optional<ToolProvider>} of the first instance found
*
* @throws NullPointerException if {@code name} is {@code null}
*/
static Optional<ToolProvider> findFirst(String name) {
Objects.requireNonNull(name);
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
return AccessController.doPrivileged(
(PrivilegedAction<Optional<ToolProvider>>) () -> {
ServiceLoader<ToolProvider> sl =
ServiceLoader.load(ToolProvider.class, systemClassLoader);
return StreamSupport.stream(sl.spliterator(), false)
.filter(p -> p.name().equals(name))
.findFirst();
});
}
}

View File

@ -108,6 +108,7 @@ module java.base {
uses java.util.spi.ResourceBundleControlProvider;
uses java.util.spi.ResourceBundleProvider;
uses java.util.spi.TimeZoneNameProvider;
uses java.util.spi.ToolProvider;
uses javax.security.auth.spi.LoginModule;

View File

@ -0,0 +1,105 @@
/*
* 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.
*
* 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.
*/
/*
* @test
* @bug 8159855
* @summary test ToolProvider SPI
* @run main/othervm ToolProviderTest
*/
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.spi.ToolProvider;
public class ToolProviderTest {
public static void main(String... args) throws Exception {
ToolProviderTest t = new ToolProviderTest();
t.run();
}
void run() throws Exception {
initServices();
System.out.println("test without security manager present:");
test();
System.setSecurityManager(new SecurityManager());
System.out.println("test with security manager present:");
test();
}
private void test() throws Exception {
ToolProvider testProvider = ToolProvider.findFirst("test").get();
int rc = testProvider.run(System.out, System.err, "hello test");
if (rc != 0) {
throw new Exception("unexpected exit code: " + rc);
}
}
private void initServices() throws IOException {
Path testClasses = Paths.get(System.getProperty("test.classes"));
Path services = testClasses.resolve(Paths.get("META-INF", "services"));
Files.createDirectories(services);
Files.write(services.resolve(ToolProvider.class.getName()),
Arrays.asList(TestProvider.class.getName()));
}
public static class TestProvider implements ToolProvider {
public TestProvider() {
checkPrivileges();
}
public String name() {
return "test";
}
public int run(PrintWriter out, PrintWriter err, String... args) {
out.println("Test: " + Arrays.toString(args));
return 0;
}
private void checkPrivileges() {
boolean haveSecurityManager = (System.getSecurityManager() != null);
try {
// validate appropriate privileges by checking access to a
// system property
System.getProperty("java.home");
if (haveSecurityManager) {
throw new Error("exception exception not thrown");
}
} catch (SecurityException e) {
if (!haveSecurityManager) {
throw new Error("unexpected exception: " + e);
}
}
}
}
}