提交 3fd20c70 编写于 作者: J jjg

8001098: Provide a simple light-weight "plug-in" mechanism for javac

Reviewed-by: mcimadamore
上级 2af67179
/*
* Copyright (c) 2005, 2012, 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 com.sun.source.util;
import java.util.ServiceLoader;
import javax.tools.StandardLocation;
/**
* The interface for a javac plug-in.
*
* <p>The javac plug-in mechanism allows a user to specify one or more plug-ins
* on the javac command line, to be started soon after the compilation
* has begun. Plug-ins are identified by a user-friendly name. Each plug-in that
* is started will be passed an array of strings, which may be used to
* provide the plug-in with values for any desired options or other arguments.
*
* <p>Plug-ins are located via a {@link ServiceLoader},
* using the same class path as annotation processors (i.e.
* {@link StandardLocation#PROCESSOR_PATH PROCESSOR_PATH} or
* {@code -processorpath}).
*
* <p>It is expected that a typical plug-in will simply register a
* {@link TaskListener} to be informed of events during the execution
* of the compilation, and that the rest of the work will be done
* by the task listener.
*
* @since 1.8
*/
public interface Plugin {
/**
* Get the user-friendly name of this plug-in.
* @return the user-friendly name of the plug-in
*/
String getName();
/**
* Invoke the plug-in for a given compilation task.
* @param task The compilation task that has just been started
* @param args Arguments, if any, for the plug-in
*/
void call(JavacTask task, String... args);
}
......@@ -58,7 +58,9 @@ public abstract class Trees {
* @throws IllegalArgumentException if the task does not support the Trees API.
*/
public static Trees instance(CompilationTask task) {
if (!task.getClass().getName().equals("com.sun.tools.javac.api.JavacTaskImpl"))
String taskClassName = task.getClass().getName();
if (!taskClassName.equals("com.sun.tools.javac.api.JavacTaskImpl")
&& !taskClassName.equals("com.sun.tools.javac.api.BasicJavacTask"))
throw new IllegalArgumentException();
return getJavacTrees(CompilationTask.class, task);
}
......
......@@ -136,6 +136,14 @@ public class BasicJavacTask extends JavacTask {
throw new IllegalStateException();
}
/**
* For internal use only. This method will be
* removed without warning.
*/
public Context getContext() {
return context;
}
/**
* For internal use only. This method will be
* removed without warning.
......
......@@ -485,22 +485,6 @@ public class JavacTaskImpl extends BasicJavacTask {
abstract void process(Env<AttrContext> env);
}
/**
* For internal use only. This method will be
* removed without warning.
*/
public Context getContext() {
return context;
}
/**
* For internal use only. This method will be
* removed without warning.
*/
public void updateContext(Context newContext) {
context = newContext;
}
/**
* For internal use only. This method will be
* removed without warning.
......
......@@ -121,9 +121,9 @@ public class JavacTrees extends DocTrees {
// called reflectively from Trees.instance(CompilationTask task)
public static JavacTrees instance(JavaCompiler.CompilationTask task) {
if (!(task instanceof JavacTaskImpl))
if (!(task instanceof BasicJavacTask))
throw new IllegalArgumentException();
return instance(((JavacTaskImpl)task).getContext());
return instance(((BasicJavacTask)task).getContext());
}
// called reflectively from Trees.instance(ProcessingEnvironment env)
......
......@@ -1040,7 +1040,8 @@ public class JavaCompiler implements ClassReader.SourceCompleter {
if (options.isSet(PROC, "none")) {
processAnnotations = false;
} else if (procEnvImpl == null) {
procEnvImpl = new JavacProcessingEnvironment(context, processors);
procEnvImpl = JavacProcessingEnvironment.instance(context);
procEnvImpl.setProcessors(processors);
processAnnotations = procEnvImpl.atLeastOneProcessor();
if (processAnnotations) {
......
......@@ -33,24 +33,29 @@ import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.ServiceLoader;
import java.util.Set;
import javax.annotation.processing.Processor;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.annotation.processing.Processor;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.file.CacheFSInfo;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.jvm.Target;
import com.sun.tools.javac.processing.AnnotationProcessingError;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.Log.WriterKind;
import com.sun.tools.javac.util.Log.PrefixKind;
import com.sun.tools.javac.processing.AnnotationProcessingError;
import com.sun.tools.javac.util.Log.WriterKind;
import static com.sun.tools.javac.main.Option.*;
/** This class provides a commandline interface to the GJC compiler.
/** This class provides a command line interface to the javac compiler.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
......@@ -423,6 +428,42 @@ public class Main {
if (batchMode)
CacheFSInfo.preRegister(context);
// invoke any available plugins
String plugins = options.get(PLUGIN);
if (plugins != null) {
JavacProcessingEnvironment pEnv = JavacProcessingEnvironment.instance(context);
ClassLoader cl = pEnv.getProcessorClassLoader();
ServiceLoader<Plugin> sl = ServiceLoader.load(Plugin.class, cl);
Set<List<String>> pluginsToCall = new LinkedHashSet<List<String>>();
for (String plugin: plugins.split("\\x00")) {
pluginsToCall.add(List.from(plugin.split("\\s+")));
}
JavacTask task = null;
Iterator<Plugin> iter = sl.iterator();
while (iter.hasNext()) {
Plugin plugin = iter.next();
for (List<String> p: pluginsToCall) {
if (plugin.getName().equals(p.head)) {
pluginsToCall.remove(p);
try {
if (task == null)
task = JavacTask.instance(pEnv);
plugin.call(task, p.tail.toArray(new String[p.tail.size()]));
} catch (Throwable ex) {
if (apiMode)
throw new RuntimeException(ex);
pluginMessage(ex);
return Result.SYSERR;
}
}
}
}
for (List<String> p: pluginsToCall) {
log.printLines(PrefixKind.JAVAC, "msg.plugin.not.found", p.head);
}
}
fileManager = context.get(JavaFileManager.class);
comp = JavaCompiler.instance(context);
......@@ -537,6 +578,14 @@ public class Main {
ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE));
}
/** Print a message reporting an uncaught exception from an
* annotation processor.
*/
void pluginMessage(Throwable ex) {
log.printLines(PrefixKind.JAVAC, "msg.plugin.uncaught.exception");
ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
}
/** Display the location and checksum of a class. */
void showClass(String className) {
PrintWriter pw = log.getWriter(WriterKind.NOTICE);
......
......@@ -393,6 +393,16 @@ public enum Option {
/* -Xjcov produces tables to support the code coverage tool jcov. */
XJCOV("-Xjcov", null, HIDDEN, BASIC),
PLUGIN("-Xplugin:", "opt.arg.plugin", "opt.plugin", EXTENDED, BASIC) {
@Override
public boolean process(OptionHelper helper, String option) {
String p = option.substring(option.indexOf(':') + 1);
String prev = helper.get(PLUGIN);
helper.put(PLUGIN.text, (prev == null) ? p : prev + '\0' + p.trim());
return false;
}
},
/* This is a back door to the compiler's option table.
* -XDx=y sets the option x to the value y.
* -XDx sets the option x to the value x.
......
......@@ -145,6 +145,7 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea
Source source;
private ClassLoader processorClassLoader;
private SecurityException processorClassLoaderException;
/**
* JavacMessages object used for localization
......@@ -155,7 +156,15 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea
private Context context;
public JavacProcessingEnvironment(Context context, Iterable<? extends Processor> processors) {
/** Get the JavacProcessingEnvironment instance for this context. */
public static JavacProcessingEnvironment instance(Context context) {
JavacProcessingEnvironment instance = context.get(JavacProcessingEnvironment.class);
if (instance == null)
instance = new JavacProcessingEnvironment(context);
return instance;
}
protected JavacProcessingEnvironment(Context context) {
this.context = context;
log = Log.instance(context);
source = Source.instance(context);
......@@ -184,6 +193,11 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea
unmatchedProcessorOptions = initUnmatchedProcessorOptions();
messages = JavacMessages.instance(context);
taskListener = MultiTaskListener.instance(context);
initProcessorClassLoader();
}
public void setProcessors(Iterable<? extends Processor> processors) {
Assert.checkNull(discoveredProcs);
initProcessorIterator(context, processors);
}
......@@ -199,6 +213,23 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea
return Collections.unmodifiableSet(platformAnnotations);
}
private void initProcessorClassLoader() {
JavaFileManager fileManager = context.get(JavaFileManager.class);
try {
// If processorpath is not explicitly set, use the classpath.
processorClassLoader = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH)
? fileManager.getClassLoader(ANNOTATION_PROCESSOR_PATH)
: fileManager.getClassLoader(CLASS_PATH);
if (processorClassLoader != null && processorClassLoader instanceof Closeable) {
JavaCompiler compiler = JavaCompiler.instance(context);
compiler.closeables = compiler.closeables.prepend((Closeable) processorClassLoader);
}
} catch (SecurityException e) {
processorClassLoaderException = e;
}
}
private void initProcessorIterator(Context context, Iterable<? extends Processor> processors) {
Log log = Log.instance(context);
Iterator<? extends Processor> processorIterator;
......@@ -217,18 +248,7 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea
processorIterator = processors.iterator();
} else {
String processorNames = options.get(PROCESSOR);
JavaFileManager fileManager = context.get(JavaFileManager.class);
try {
// If processorpath is not explicitly set, use the classpath.
processorClassLoader = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH)
? fileManager.getClassLoader(ANNOTATION_PROCESSOR_PATH)
: fileManager.getClassLoader(CLASS_PATH);
if (processorClassLoader != null && processorClassLoader instanceof Closeable) {
JavaCompiler compiler = JavaCompiler.instance(context);
compiler.closeables = compiler.closeables.prepend((Closeable) processorClassLoader);
}
if (processorClassLoaderException == null) {
/*
* If the "-processor" option is used, search the appropriate
* path for the named class. Otherwise, use a service
......@@ -239,14 +259,15 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea
} else {
processorIterator = new ServiceIterator(processorClassLoader, log);
}
} catch (SecurityException e) {
} else {
/*
* A security exception will occur if we can't create a classloader.
* Ignore the exception if, with hindsight, we didn't need it anyway
* (i.e. no processor was specified either explicitly, or implicitly,
* in service configuration file.) Otherwise, we cannot continue.
*/
processorIterator = handleServiceLoaderUnavailability("proc.cant.create.loader", e);
processorIterator = handleServiceLoaderUnavailability("proc.cant.create.loader",
processorClassLoaderException);
}
}
discoveredProcs = new DiscoveredProcessors(processorIterator);
......@@ -1473,13 +1494,19 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea
}
/**
* For internal use only. This method will be
* removed without warning.
* For internal use only. This method may be removed without warning.
*/
public Context getContext() {
return context;
}
/**
* For internal use only. This method may be removed without warning.
*/
public ClassLoader getProcessorClassLoader() {
return processorClassLoader;
}
public String toString() {
return "javac ProcessingEnvironment";
}
......
......@@ -99,6 +99,10 @@ javac.opt.arg.release=\
<release>
javac.opt.arg.number=\
<number>
javac.opt.plugin=\
Name and optional arguments for a plug-in to be run
javac.opt.arg.plugin=\
"name args"
## extended options
......@@ -185,6 +189,8 @@ javac.err.file.not.directory=\
not a directory: {0}
javac.err.file.not.file=\
not a file: {0}
javac.msg.plugin.not.found=\
plug-in not found: {0}
## messages
javac.msg.usage.header=\
......@@ -212,6 +218,10 @@ javac.msg.proc.annotation.uncaught.exception=\
\n\nAn annotation processor threw an uncaught exception.\n\
Consult the following stack trace for details.\n
javac.msg.plugin.uncaught.exception=\
\n\nA plugin threw an uncaught exception.\n\
Consult the following stack trace for details.\n
javac.msg.resource=\
\n\nThe system is out of resources.\n\
Consult the following stack trace for details.\n
......
/* /nodynamiccopyright */
public class Identifiers {
public double E = Math.E;
public double PI = Math.PI;
public double PIE = PI + E;
}
Identifiers.java:3: Note: type is ()void
public class Identifiers {
^
Identifiers.java:4: Note: type is double
public double E = Math.E;
^
Identifiers.java:4: Note: type is java.lang.Math
public double E = Math.E;
^
Identifiers.java:5: Note: type is double
public double PI = Math.PI;
^
Identifiers.java:5: Note: type is java.lang.Math
public double PI = Math.PI;
^
Identifiers.java:6: Note: type is double
public double PIE = PI + E;
^
Identifiers.java:6: Note: type is double
public double PIE = PI + E;
^
Identifiers.java:5: Note: type is double
public double PI = Math.PI;
^
Identifiers.java:6: Note: type is double
public double PIE = PI + E;
^
/*
* Copyright (c) 2012, 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.
*/
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;
import java.util.regex.Pattern;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
public class ShowTypePlugin implements Plugin {
public String getName() {
return "showtype";
}
public void call(JavacTask task, String... args) {
Pattern pattern = null;
if (args.length == 1)
pattern = Pattern.compile(args[0]);
task.addTaskListener(new PostAnalyzeTaskListener(task, pattern));
}
private static class PostAnalyzeTaskListener implements TaskListener {
private final ShowTypeTreeVisitor visitor;
PostAnalyzeTaskListener(JavacTask task, Pattern pattern) {
visitor = new ShowTypeTreeVisitor(task, pattern);
}
@Override
public void started(TaskEvent taskEvent) { }
@Override
public void finished(TaskEvent taskEvent) {
if (taskEvent.getKind().equals(TaskEvent.Kind.ANALYZE)) {
CompilationUnitTree compilationUnit = taskEvent.getCompilationUnit();
visitor.scan(compilationUnit, null);
}
}
}
private static class ShowTypeTreeVisitor extends TreePathScanner<Void, Void> {
private final Trees trees;
private final Pattern pattern;
private CompilationUnitTree currCompUnit;
ShowTypeTreeVisitor(JavacTask task, Pattern pattern) {
trees = Trees.instance(task);
this.pattern = pattern;
}
@Override
public Void visitCompilationUnit(CompilationUnitTree tree, Void ignore) {
currCompUnit = tree;
return super.visitCompilationUnit(tree, ignore);
}
@Override
public Void visitIdentifier(IdentifierTree tree, Void ignore) {
show(tree, tree.getName());
return super.visitIdentifier(tree, ignore);
}
@Override
public Void visitMemberSelect(MemberSelectTree tree, Void ignore) {
show(tree, tree.getIdentifier());
return super.visitMemberSelect(tree, ignore);
}
void show(Tree tree, CharSequence name) {
if (pattern == null || pattern.matcher(name).matches()) {
TypeMirror type = trees.getTypeMirror(getCurrentPath());
trees.printMessage(Kind.NOTE, "type is " + type, tree, currCompUnit);
}
}
}
}
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
/*
* Copyright (c) 2012, 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 8001098
* @summary Provide a simple light-weight "plug-in" mechanism for javac
*/
public class Test {
public static void main(String... args) throws Exception {
new Test().run();
}
final File testSrc;
final File pluginSrc;
final File pluginClasses ;
final File pluginJar;
final List<String> ref1;
final List<String> ref2;
final JavaCompiler compiler;
final StandardJavaFileManager fm;
Test() throws Exception {
testSrc = new File(System.getProperty("test.src"));
pluginSrc = new File(testSrc, "ShowTypePlugin.java");
pluginClasses = new File("plugin");
pluginJar = new File("plugin.jar");
ref1 = readFile(testSrc, "Identifiers.out");
ref2 = readFile(testSrc, "Identifiers_PI.out");
compiler = ToolProvider.getSystemJavaCompiler();
fm = compiler.getStandardFileManager(null, null, null);
}
void run() throws Exception {
// compile the plugin explicitly, to a non-standard directory
// so that we don't find it on the wrong path by accident
pluginClasses.mkdirs();
compile("-d", pluginClasses.getPath(), pluginSrc.getPath());
writeFile(new File(pluginClasses, "META-INF/services/com.sun.source.util.Plugin"),
"ShowTypePlugin\n");
jar("cf", pluginJar.getPath(), "-C", pluginClasses.getPath(), ".");
testCommandLine("-Xplugin:showtype", ref1);
testCommandLine("-Xplugin:showtype PI", ref2);
testAPI("-Xplugin:showtype", ref1);
testAPI("-Xplugin:showtype PI", ref2);
if (errors > 0)
throw new Exception(errors + " errors occurred");
}
void testAPI(String opt, List<String> ref) throws Exception {
File identifiers = new File(testSrc, "Identifiers.java");
fm.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, Arrays.asList(pluginJar));
fm.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(new File(".")));
List<String> options = Arrays.asList(opt);
Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(identifiers);
System.err.println("test api: " + options + " " + files);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
boolean ok = compiler.getTask(pw, fm, null, options, null, files).call();
String out = sw.toString();
System.err.println(out);
if (!ok)
error("testCommandLine: compilation failed");
checkOutput(out, ref);
}
void testCommandLine(String opt, List<String> ref) {
File identifiers = new File(testSrc, "Identifiers.java");
String[] args = {
"-d", ".",
"-processorpath", pluginJar.getPath(),
opt,
identifiers.getPath() };
System.err.println("test command line: " + Arrays.asList(args));
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
int rc = com.sun.tools.javac.Main.compile(args, pw);
String out = sw.toString();
System.err.println(out);
if (rc != 0)
error("testCommandLine: compilation failed");
checkOutput(out, ref);
}
private void checkOutput(String out, List<String> ref) {
List<String> lines = Arrays.asList(out
.replaceAll(".*?([A-Za-z.]+:[0-9]+: .*)", "$1") // remove file directory
.split("[\r\n]+")); // allow for newline formats
if (!lines.equals(ref)) {
error("unexpected output");
}
}
private void compile(String... args) throws Exception {
System.err.println("compile: " + Arrays.asList(args));
int rc = com.sun.tools.javac.Main.compile(args);
if (rc != 0)
throw new Exception("compiled failed, rc=" + rc);
}
private void jar(String... args) throws Exception {
System.err.println("jar: " + Arrays.asList(args));
boolean ok = new sun.tools.jar.Main(System.out, System.err, "jar").run(args);
if (!ok)
throw new Exception("jar failed");
}
private List<String> readFile(File dir, String name) throws IOException {
return Files.readAllLines(new File(dir, name).toPath(), Charset.defaultCharset());
}
private void writeFile(File f, String body) throws IOException {
f.getParentFile().mkdirs();
try (FileWriter out = new FileWriter(f)) {
out.write(body);
}
}
private void error(String msg) {
System.err.println(msg);
errors++;
}
int errors;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册