I have a Javac Plug-in (implementation of com.sun.source.util.Plugin
) and I want it to alter parser behavior.
I can extend com.sun.tools.javac.parser.ParserFactory
and register it in my com.sun.tools.javac.util.Context
when my plug-in is initialized, but there is a problem:
Plugins are initialized right after initialization of JavacProcessingEnvironment
which creates all things necessary for compilation including ParserFactory
, but I can only add my ParserFactory
to context, not overwrite existing one.
Here is a piece of code I am interested in (com.sun.tools.javac.api.BasicJavacTask
):
public void initPlugins(Set<List<String>> pluginOpts) {
PlatformDescription platformProvider = context.get(PlatformDescription.class);
if (platformProvider != null) {
for (PluginInfo<Plugin> pluginDesc : platformProvider.getPlugins()) {
//Init platform plugins
}
}
if (pluginOpts.isEmpty())
return;
Set<List<String>> pluginsToCall = new LinkedHashSet<>(pluginOpts);
//JavacProcessingEnvironment is created here
JavacProcessingEnvironment pEnv = JavacProcessingEnvironment.instance(context);
//Since here, following will fail:
//context.put(ParserFactory.parserFactoryKey, new MyParserFactory())
ServiceLoader<Plugin> sl = pEnv.getServiceLoader(Plugin.class);
for (Plugin plugin : sl) {
for (List<String> p : pluginsToCall) {
if (plugin.getName().equals(p.head)) {
pluginsToCall.remove(p);
try {
//My plugin is initialized here
plugin.init(this, p.tail.toArray(new String[p.tail.size()]));
} catch (RuntimeException ex) {
throw new PropagatedException(ex);
}
break;
}
}
}
}
If I had an opportunity to add my plugin to platformProvider
, it will solve my problem by earlier initialization of plugin.
What am I doing wrong? Is there any way to register my own parser factory? Or maybe the com.sun.tools.javac.util.Context
is not intended to use by plugin developers, but only by JDK developers?
Found out the least hacky way, as it seems that com.sun.tools.javac.util.Context
functionality is intended for JDK developers (oh rly? I should have guess it when was adding 9 --add-exports=jdk.compiler/com.sun.tools.javac.*=mymodule
arguments)
/*
When I try to just put my factory here, I am getting
AssertionError: duplicate context value
but I can remove base factory by passing null
*/
context.put(parserFactoryKey, (ParserFactory) null);
MyParserFactory factory = new MyParserFactory(context);
context.put(parserFactoryKey, factory);
/*
Now context contains my implementation of ParserFactory, but I
also need to inject it into JavaCompiler which is the only object
(FOR MY JDK: check your compiler behavior carefully with debugger)
that have already got base factory with ParserFactory.instance()
but didn't use it yet.
*/
try {
Field f = JavaCompiler.class.getDeclaredField("parserFactory");
f.setAccessible(true);
f.set(JavaCompiler.instance(context), factory);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}