扩展 Javac 解析器行为

Extend Javac parser behavior

我有一个 Javac 插件(com.sun.source.util.Plugin 的实现),我希望它能改变解析器的行为。

我可以扩展com.sun.tools.javac.parser.ParserFactory并在我的插件初始化时将其注册到我的com.sun.tools.javac.util.Context中,但是有一个问题: 插件在 JavacProcessingEnvironment 初始化后立即初始化,这会创建编译所需的所有内容,包括 ParserFactory,但我只能将我的 ParserFactory 添加到上下文中,而不是覆盖现有的

这是我感兴趣的一段代码(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;
            }
        }
    }
}

如果我有机会将我的插件添加到 platformProvider,它会通过更早的插件初始化来解决我的问题。

我做错了什么?有没有办法注册我自己的解析器工厂?或者 com.sun.tools.javac.util.Context 不打算供插件开发人员使用,而仅供 JDK 开发人员使用?

找到了最简单的方法,因为 com.sun.tools.javac.util.Context 功能似乎是为 JDK 开发人员准备的(哦,是吗?我在添加 9 个 --add-exports=jdk.compiler/com.sun.tools.javac.*=mymodule 参数时应该猜到了)

    /*
    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);
    }