In JDK, there is a set of related compilation APIs that can initiate the compilation process in Java, parse Java source files and obtain its syntax tree. This complete set of APIs is included in the tools.jar of JDK (can be found in /Library/Java/JavaVirtualMachines/jdk_version/Contents/Home/lib under OSX), but this is not a public API in Oracle and OpenJDK releases, so there is no official official documentation to explain this set of APIs. However, many projects have used this API to do a lot of things. For example, the famous lombok used this API to modify the syntax tree in the source code during the Annotation Processing stage. The final result is equivalent to inserting new code directly into the source file!
Since this set of APIs currently lack relevant documents, it is difficult to use. For example, parse all variables in the source code and print them out:
public class JavaParser { private static final String path = "User.java"; private JavacFileManager fileManager; private JavaCTool javacTool; public JavaParser() { Context context = new Context(); fileManager = new JavacFileManager(context, true, Charset.defaultCharset()); javacTool = new JavacTool(); } public void parseJavaFiles() { Iterable<!--? extends JavaFileObject--> files = fileManager.getJavaFileObjects(path); JavaCompiler.CompilationTask compilationTask = javacTool.getTask(null, fileManager, null, null, null, files); JavaCTask javacTask = (JavacTask) compilationTask; try { Iterable<!--? extends CompilationUnitTree--> result = javacTask.parse(); for (CompilationUnitTree tree : result) { tree.accept(new SourceVisitor(), null); } } catch (IOException e) { e.printStackTrace(); } } static class SourceVisitor extends TreeScanner<void, void=""> { private String currentPackageName = null; @Override public Void visitCompilationUnit(CompilationUnitTree node, Void aVoid) { return super.visitCompilationUnit(node, aVoid); } @Override public Void visitVariable(VariableTree node, Void aVoid) { formatPtrln("variable name: %s, type: %s, kind: %s, package: %s", node.getName(), node.getType(), node.getKind(), currentPackageName); return null; } } public static void formatPtrln(String format, Object... args) { System.out.println(String.format(format, args)); } public static void main(String[] args) { new JavaParser().parseJavaFiles(); }}</void,>The code of User.java is as follows:
package com.ragnarok.javaparser; import com.sun.istack.internal.Nullable;import java.lang.Override; public class User { @Nullable private String foo = "123123"; private Foo a; public void UserMethod() {} static class Foo { private String fooString = "123123"; public void FooMethod() {} }}The result of executing the above JavaParser is as follows:
variable: foo, annotaion: Nullablevariable name: foo, type: String, kind: VARIABLE, package: com.ragnarok.javaparserver variable name: a, type: Foo, kind: VARIABLE, package: com.ragnarok.javaparser
Here we first parse the source file through JavaCompiler.CompilationTask, and then use the custom SourceVisitor (inherited from TreeScanner) to access the structure of the source code. In the SourceVisitor class, we overload visitVariable to parse a compilation unit (single source code file) and access all variables. It can be seen here that we cannot get the fully qualified name of this variable type (including package name), and can only get the corresponding simple name. Therefore, the determination of the type requires external implementation to determine it by itself. For example, you can record the package name where the class is located, recursively search the entire source code directory to track the fully qualified name of all classes, and find whether the import contains the corresponding type, etc.
In addition to visitVariable method, TreeScanner also contains a large number of other visitXYZ methods. For example, you can traverse all imports, method definitions, Annotation, etc. For more specific, you can view the source code about this in OpenJDK.
Here we will take another example, overloading the visitClass method to access all inner classes and the class itself:
@Overridepublic Void visitClass(ClassTree node, Void aVoid) { formatPtrln("class name: %s", node.getSimpleName()); for (Tree member : node.getMembers()) { if (member instanceof VariableTree) { VariableTree variable = (VariableTree) member; List<!--? extends AnnotationTree--> annotations = variable.getModifiers().getAnnotations(); if (annotations.size() > 0) { formatPtrln("variable: %s, annotaion: %s", variable.getName(), annotations.get(0).getAnnotationType()); } else { formatPtrln("variable: %s", variable.getName()); } } } return super.visitClass(node, aVoid); }Here we simply print the class name and variable name, type, and annotation type. Execute the above code, and the result is as follows:
class name: Uservariable: foo, annotaion: Nullablevariable: class name: Foovariable: fooString
It can be seen that we have printed out the class name and the variables in the class. In the visitClass method, we can get all members of the class through the getMembers method, including variables, methods, annotation, etc., which correspond to different types. For example, variables correspond to VariableTree type, and method corresponds to MethodTree type.
In general, although the use is not particularly complicated in fact, it has caused great obstacles to use due to the lack of documentation. And what we are introducing is only a small part of this API. I will continue to study the related functions of this API in the future.
The above is a compilation of JDK's Parser's data to parse Java source code. We will continue to add relevant information in the future. Thank you for your support for this site!