【问题标题】:make javac compile from a non-java dependency使 javac 从非 java 依赖项编译
【发布时间】:2014-08-06 10:44:28
【问题描述】:

我们的目标是以编程方式使用 javac(没有其他编译器)来编译扩展另一个类的 Java 类,该类不作为 java 源代码或字节码存在,也不能作为这些类提供。 Java 类作为源文件存在,或者作为字符串存在于内存中(我知道如何从字符串编译)。

我认为挂钩到预编译类的查找是我需要的。但到目前为止,我无法找到这部分。因此,如果我知道 javac 究竟是如何查找预编译类的,我如何挂钩并提供我自己的表示,那么这个问题就得到了回答。

或任何其他允许我动态提供所需依赖项的方式...

编辑: 既然提到了 Groovy 并且目的受到质疑,那么让我举个例子...... 想象一下,您在 Groovy 中有一个名为 G 的类,具有 J 类的字段,它是一个 Java 类,J 扩展了 G。没有 J 就无法编译 G,没有 G 也无法编译 J。但是我有 G 的 AST,如果我可以将 AST 与 javac 连接起来,我将能够编译 J,然后在 Groovy 编译器 G 中编译 - 或者相反。目前这是通过生成存根绕过的,但我正在寻找更好的解决方案。

编辑 2: 说得一清二楚。这个问题的最终目标是让 groovy 编译器和 javac 编译器以某种方式相互交谈,如果它们有某个类,它们可以相互告知,然后让另一个编译器知道该特定类。让我再说一遍,字节码中的存根由于未解析的类而无法工作。在源代码中,它们的工作方式取决于进口解决方案至少相似。但是由于 Groovy 编译器的性质,它实际上有点类似于 javac 所做的处理,我们必须在相当早的阶段生成这些源存根,对于您可以在 groovy 中应用的大多数 ast 转换来说还为时过早。这是一个问题

【问题讨论】:

  • 听起来很奇怪。你将如何开设这门课?再次没有基类?这一次您将需要连接到 JVM。您可能想解释一下您的主要任务是什么,您是如何得出这个解决方案的,以及为什么您没有基类。
  • 我更新了问题以尝试解释一下情况。

标签: java javac


【解决方案1】:

如果第一条评论是正确的,并且您在没有 B 的源代码或字节码的情况下尝试编译扩展 B 的类 A,那么我认为答案是“您不能”。如果您考虑编译子类意味着什么,您会意识到编译器需要来自超类的详细信息——有哪些方法,有哪些抽象方法,在跳转表中放置什么,哪些受保护的变量和方法可能被引用,等等。Java 仍然是一种强类型语言(至少在撰写本文时)。

【讨论】:

  • 只是在这里发表评论,同时我对问题进行了 2 次编辑以澄清情况,并且我有点拥有超级类。
【解决方案2】:

我可以提出以下建议。您确实不必提供“真正的”基类。您必须为基类提供

  1. 根据需要使用相同的完全限定名称。
  2. 如果它们是从子类调用的,则相同的构造函数
  3. 如果从子类调用或覆盖它们,则使用相同的方法。

因此,您可以生成满足所有这些要求的类,在类路径中使用此基类编译您的子类,然后删除此假基类。

如何创建这样的类?您可以生成源代码并编译它,也可以使用ASM 直接生成字节码。

问题是如何获取类的所有元素:必需的构造函数和超类方法。我不了解 Groovy,但我希望您可以使用反射从 Groovy 类中获取所有这些信息。

编辑

其他解决方案是使用接口并将 Groovy 和 Java 代码解耦。只需在 Java 中定义将从 Groovy 引用的接口。该接口不会有任何其他依赖,可以先编译。然后编译 Groovy 代码。然后编译扩展 Groovy 类的 java 类。这有什么问题?

【讨论】:

  • 您建议生成存根。这已经是我们所做的(尽管基于源代码)。这有很多问题,其中之一是我们必须在 groovy 编译器的早期阶段运行存根生成,这意味着并非编译器所做的一切都会反映在存根中。这就是为什么这种方法有效,但不是 100% 令人满意。我最终的目标是编译器之间的双向通信。我可以实现 javac tress 到 groovyc ast,但不能反过来。这就是为什么我有这个问题
  • @blackdrag,请在我的答案底部查看我的其他想法。
  • 您的编辑是在我的评论之后;)强迫用户编写不需要的接口对于无缝集成非常不利。要么你会产生两个世界,在其中,Groovy 注定只能成为前端解决方案(因此使用不多),要么一切都将立即用 Java 结束(然后使用更少)。
【解决方案3】:

看来我现在可以自己回答这个问题了。通过直接使用 com.sun.tools.javac.main.JavaCompiler,我可以提供一个上下文,在其中设置自定义 ClassReader。然后可以覆盖 loadClass 方法,以提供我自己的 ClassSymbol。我用这段代码来提供 ClassSymbol 的类型

        // ClassSymbol cs is from super.loadClass
        // Context context is same as used for creating the compiler
        Type.ClassType newClassType = new Type.ClassType(Type.noType, List.<Type>nil(), cs);
        cs.type = newClassType;
        newClassType.tsym = cs;
        Names names = Names.instance(context);
        Symtab symtab = Symtab.instance(context);
        newClassType.supertype_field = symtab.objectType;
        cs.kind = Kinds.TYP;
        Scope members = new Scope(cs);
        cs.members_field = members;
        Type.MethodType mt = new Type.MethodType(List.<Type>nil(), symtab.voidType, List.<Type>nil(), symtab.methodClass);
        Symbol.MethodSymbol constructor = new Symbol.MethodSymbol(Flags.PUBLIC, names.init, mt, cs);
        members.enter(constructor);

代码将为缺少的类型创建一个幻像类型(扩展 Object),并提供一个没有参数和主体的构造函数。不确定这是否是最好的方法,但它足以让 javac 运行

【讨论】:

  • 幻像类型可防止typeNotFound 抛出。但是methodNotFound 呢?如果创建的类型被其他缺少成员的类引用,它会抛出吗?
猜你喜欢
  • 1970-01-01
  • 2015-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-04
  • 2015-10-03
  • 2014-03-22
  • 2020-05-04
相关资源
最近更新 更多