JDK Parser에서 Java 소스 코드 상세 정보 확인

JDK에서는 Java에서 컴파일링 프로세스를 시작하고 Java 원본 파일을 해석한 다음 문법 트리를 가져와서 JDK의 tools에서 가져옵니다.jar(OSX에서/Library/Java/JavaVirtualMachines/jdk_version/Contents/Home/lib에서 찾을 수 있음)에는 이 전체 API가 포함되어 있지만, 이것은 Oracle과 OpenJDK가 발표한 공개 API가 아니기 때문에 이 API에 대해서는 공식적인 공식 문서가 없습니다.그러나 많은 프로젝트가 이 API를 이용하여 많은 일을 했다. 예를 들어 유명한 lombok이 이 API를 사용하여 Annotation Processing 단계에서 원본 코드의 문법 트리를 수정했다. 최종 결과는 원본 파일에 새로운 코드를 직접 삽입한 것과 같다!
이 API는 현재 관련 문서가 부족하기 때문에 사용하기가 비교적 어렵다. 예를 들어 소스 코드의 모든 변수를 분석하고 인쇄하는 것이다.

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,>

여기서 사용자.java 코드는 다음과 같습니다.

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() {}
 }
}

위의 JavaParser 결과는 다음과 같습니다.

variable: foo, annotaion: Nullable
variable name: foo, type: String, kind: VARIABLE, package: com.ragnarok.javaparser
variable name: a, type: Foo, kind: VARIABLE, package: com.ragnarok.javaparser

여기서 우리는 먼저 JavaCompiler를 통해CompilationTask는 원본 파일을 해석한 후에 사용자 정의 SourceVisitor(TreeScanner로부터 계승)를 사용하여 원본 코드의 구조에 접근합니다. SourceVisitor 클래스에서visitVariable을 다시 불러와서 컴파일된 단원(단일 원본 코드 파일)을 해석하고 그 중의 모든 변수에 접근합니다. 여기서 알 수 있듯이 우리는 이 변수 유형의 전체 한정된 이름(패키지 이름 포함)을 얻을 수 없습니다.대응하는 간단한 이름만 얻을 수 있기 때문에 유형의 확정은 외부에서 자체적으로 확정해야 한다. 예를 들어 클래스가 있는 패키지 이름을 기록하고 전체 원본 코드 디렉터리를 검색하여 모든 클래스의 전체 한정된 이름을 추적하고import에 대응하는 유형이 포함되어 있는지 찾아낼 수 있다.
TreeScanner에는 visitVariable 방법 외에 대량의 다른visitXYZ 방법이 포함되어 있다. 예를 들어 모든 import, 방법 정의, Annotation 등을 두루 훑어볼 수 있고 OpenJDK에서 이것에 관한 원본 코드를 구체적으로 볼 수 있다.
여기서 다음 또 다른 예를 살펴보면visitClass 방법을 다시 불러와 모든 내부 클래스와 클래스 자체를 방문한다.

@Override
public 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);
 }

클래스 이름과 변수의 이름, 형식, annotation 형식을 간단하게 인쇄하고 위의 코드를 실행합니다. 결과는 다음과 같습니다.

class name: User
variable: foo, annotaion: Nullable
variable: a
class name: Foo
variable: fooString



우리가 클래스 이름과 클래스 중의 변수를 모두 인쇄한 것을 알 수 있다.한편visitClass 방법에서 우리는 getMembers 방법을 통해 클래스의 모든 구성원을 얻을 수 있다. 변수, 방법, annotation 등을 포함하여 각각 다른 유형에 대응한다. 예를 들어 변수는 VariableTree 유형에 대응하고 방법은 대응하는 MethodTree 유형에 대응한다.
어쨌든 실제 사용은 특별히 복잡하지 않지만 문서가 부족하기 때문에 사용에 큰 장애를 초래했다. 그리고 현재 소개한 것은 이 API의 일부분일 뿐이다. 이후에 나는 이 API와 관련된 함수를 계속 연구할 것이다.
이상은 JDK의 Parser가 자바 원본 코드를 해석하는 자료 정리입니다. 후속으로 관련 자료를 계속 보충합니다. 본 사이트에 대한 지지에 감사드립니다!

좋은 웹페이지 즐겨찾기