扩展Javac解析器行为

时间:2018-12-06 00:03:25

标签: java plugins javac java-9 annotation-processing

我有一个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开发人员使用?

1 个答案:

答案 0 :(得分:0)

发现了最不容易破解的方法,因为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);
    }