Edit java source files programmatically

Deadly 提交于 2019-12-11 04:05:56

问题


I'm looking for a way to edit .java files programmatically without resorting to unstable RegExes. Since I'm editing .java files, not .class files I do not want any Byte-code manipulation tools.

I need something that:

  • Is IDE-independent (hence no ASTParser. I want to automate it on CI, so AST is out unless there's a standalone version)
  • Allows me to read a .java file, add an annotation to a method and save it - hence pure source code generation tools (CodeModel comes to mind) are not enough
  • Is not too complicated and/or dedicated for Java - hence no ANTLR

So in short, something to reproduce this scenario:

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

I have tried Java reflection, but it can't do it (also since it's byte-code analysis, it can't parse a method's body). Similarly with java model API. I tried to get AST to work standalone but I failed (maybe there is a way?)

If there is absolutely no way or tool to do it, is it possible to do with regexes in a unique and stable way? (i.e. no possible Java sourcecode would be an input for operation other than in above pseudo-code). If so, please give me an example of such.

Also, I do not need to compile it, after pushing the changes, CI will do it for me.


回答1:


You can do this reliably with a program transformation system (PTS). These are IDE-independent.

One of these is our DMS Software Reengineering Toolkit. OP can accomplish his specific task with code something like the following DMS meta-program: (not tested and doesn't handle all the edge cases):

 (= 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's metaprogramming language looks like Lisp, with prefix operators. (Get over it :-) ParseFile reads a source file and builds an AST, parked in parse_Tree. ScanTree scans tree looking for a point where the supplied predicate ("Registry:Pattern (. `any_method'") is true, and returns a matching subtree or null. Registry:PatternMatch checks that a pattern predicate is true at the root of the specified tree. Registry:ApplyTransform applies a source-to-source transformation to modify the tree.

This metaprogram is supported by a set of named patterns, which make it easy to express tests/transforms on tree without knowing every last detail of the tree structure. These are oversimplified for presentation purposes:

 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 ";

The quote marks are metaquotes; they delineate the boundaries between the syntax of the pattern matching language as a whole, and code fragments written in the target language (in this case, Java, because of the domain declaration). Inside the meta quotes is target (Java) language syntax, with escaped identifiers representing pattern variables that correspond to specific tree node types. You have to know the rough structure of the grammar to write these, but notice we didn't really dive into details of how annotations or anything is formed.

Arguably the "any_method" and "TestClass" patterns could be folded into one (in fact, just the TestClass pattern itself, since it is pure specialization of "any_method".

The last rule (the others are patterns, only meant for matching) says, "if you see X, replace it by Y". What the specific rule does is pattern match to the method with some list of annotations, and add another one.

This is the way to reliable program transformations. If you don't want to use DMS (a commercial product), check out the Wikipedia page for alternatives.




回答2:


Look up javax.lang.model and the annotation processing API, javax.annotation.processing. This lets you write plugins to the javac compiler in a standardized way, all compilers supports it. You can find tutorials and talks that highlight this online.

There are a couple of limitations, for example I don't think you can rewrite the source of a file, but you can generate a new file (or class) with the annotation present. Also you can't model code inside method bodies.



来源:https://stackoverflow.com/questions/24185601/edit-java-source-files-programmatically

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!