以编程方式编辑java源文件

时间:2014-06-12 13:24:54

标签: java reflection abstract-syntax-tree

我正在寻找一种以编程方式编辑.java文件的方法,而不需要使用不稳定的RegExes。由于我正在编辑.java文件,而不是.class文件,我不想要任何字节码操作工具。

我需要一些东西:

  • 与IDE无关(因此没有ASTParser。我想在CI上自动执行它,因此除非有独立版本,否则AST已关闭)
  • 允许我读取.java文件,为方法添加注释并保存它 - 因此纯粹的源代码生成工具(想到CodeModel)是不够的
  • 不是太复杂和/或专用于Java - 因此没有ANTLR

简而言之,要重现这种情况:

File f = new File("path/to/.java");
CodeParser p = CodeParser.parse(f);
Method m = p.getMethods.get(0);
if (m.getBody().contains("abcdef") 
     && m.getAnnotation.getClass().equals(Test.class)){
   m.addAnnotation(MyAnnotation.class);
}
p.saveEdits(f);

我尝试过Java反射,但它无法做到(因为它的字节码分析,它不能解析方法的主体)。与java模型API类似。我试图让AST独立工作,但我失败了(也许有办法?)

如果完全没有办法或工具,是否可以以独特而稳定的方式处理正则表达式? (即,没有可能的Java源代码将是除上述伪代码之外的操作的输入)。如果是这样,请举个例子。

另外,我不需要编译它,推送更改后,CI会为我做。

2 个答案:

答案 0 :(得分:1)

您可以使用program transformation system(PTS)可靠地执行此操作。这些是独立于IDE的。

其中一个是我们的DMS软件重组工具包。 OP可以使用类似下面的DMS元程序的代码来完成他的特定任务:(未经过测试,并且不处理所有边缘情况):

 (= parse_Tree  (Domains:Java:Parser:ParseFile (. "path/to/.java")))
 (local (= [method_tree AST:Node] (AST:ScanTree parse_Tree (Registry:Pattern (. `any_method'))
      (ifthen (&& (~= method_tree AST:NullTree)
                  (Registry:PatternMatch method_tree (. `TestClass'))
                  (~= AST:NullTree (AST:ScanTree method_tree 
                                       (Registry:Pattern (. `abcdef_identifier'))))
          (Registry:ApplyTransform method_tree (. `insert_MyAnnotation'))
      )ifthen
 )local
 (Registry:PrettyPrintToFile method_tree (. "path/to/.java"))

DMS的元编程语言看起来像Lisp,带有前缀运算符。 (克服它:-) ParseFile读取源文件并构建一个驻留在parse_Tree中的AST。 ScanTree扫描树,查找提供的谓词("注册表:模式(.`any_method'")为真的点,并返回匹配的子树或null。 注册表:PatternMatch检查指定树的根处的模式谓词是否为true。注册表:ApplyTransform应用源到源转换来修改树。

这个元程序由一组命名模式支持,这使得在不知道树结构的每个最后细节的情况下在树上表达测试/变换变得容易。 出于演示目的,这些过于简单:

 default domain Java~v7;

 pattern any_method(p: path_to_name, name: method_name, args: arguments,
                    b: body, a: annotations):declaration =
    " \p \name(\args) \a \b ";  -- doesn't handle non-functions but easily adjusted

 pattern TestClass(p: path_to_name, name: method_name, args: arguments,
                    b: body, a: annotations):declaration =
    " \p \name(\args) [Test.class] \b ";

 pattern abcdef_identifier():IDENTIFIER =
      "abcdef";

 rule insert_MyAnnotation(p: path_to_name, name: method_name, args: arguments,
                          b: body, a: annotations):declaration =
    " \p \name(\args) \a \b "
    ->
    " \p \name(\args) \a [myAnnotation] \b ";

引号是 metaquotes ;它们描述了模式匹配语言整体语法与用目标语言编写的代码片段(在本例中为Java,因为域声明)之间的界限。元引号内部是目标(Java)语言语法,其中转义标识符表示与特定树节点类型对应的模式变量。你必须知道写这些语法的粗略结构,但请注意我们并没有真正深入研究注释或任何东西是如何形成的。

可以说是" any_method"和" TestClass"模式可以折叠成一个(事实上,只是TestClass模式本身,因为它是" any_method"的纯粹专业化。

最后的规则(其他模式,仅用于匹配)说,"如果您看到X,请将其替换为Y"。具体规则的作用是使用一些注释列表对方法进行模式匹配,然后添加另一个注释。

这是可靠程序转换的方法。如果您不想使用DMS(商业产品),请查看维基百科页面以了解替代方案。

答案 1 :(得分:-1)

查找javax.lang.model和注释处理API,javax.annotation.processing。这允许您以标准化方式将插件编写到javac编译器,所有编译器都支持它。您可以在线找到强调此内容的教程和讲座。

有一些限制,例如我认为您不能重写文件的来源,但您可以生成带有注释的新文件(或类)。此外,您无法在方法体内建模代码。