Search code examples
javapluginsjavacjava-9annotation-processing

Extend Javac parser behavior


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?


Solution

  • 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);
        }