首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

深度探索 Eclipse JDT ASTParser:從零構建 Java 類、編譯運行、解析遍歷全攻略

     編輯:Java 相關知識

Eclipse JDT (Java Development Tools) 提供強大的 AST (Abstract Syntax Tree) API,支持 Java 代碼的程序化構建、解析、編譯、運行。本文通過兩個示例項目 ASTMain (構建並運行類) 和 ASTExplorer (解析並可視化 AST) 系統講解核心用法。

一、 核心概念

  • ASTParser:解析器入口,支持 JLS2 (Java 1.4)、JLS3 (Java 1.5+)
  • CompilationUnit:AST 根節點,代表一個 .java 文件
  • ASTNode:所有語法節點基類 (TypeDeclaration, MethodDeclaration, Statement, Expression 等)
  • AST:節點工廠,負責創建各類節點 (ast.newTypeDeclaration() 等)
  • IDocument / TextEdit:JFace 文檔模型,用於將 AST 修改合並回源碼字符串
  • Compiler (internal):Eclipse 內部編譯器,可直接編譯 AST 節點無需寫磁盤

二、 從零構建 Java 類

1. 初始化解析器與編譯單元

ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource("".toCharArray()); // 必須設置源,否則訪問單元拋異常
CompilationUnit unit = (CompilationUnit) parser.createAST(null);
unit.recordModifications(); // 啟用修改記錄,後續 rewrite 需要
AST ast = unit.getAST(); // 獲取節點工廠

2. 創建包聲明

PackageDeclaration pkg = ast.newPackageDeclaration();
unit.setPackage(pkg);
pkg.setName(ast.newSimpleName("astexplorer"));

3. 創建類與 Javadoc

TypeDeclaration cls = ast.newTypeDeclaration();
cls.setName(ast.newSimpleName("SampleComposite"));
cls.setModifiers(Modifier.PUBLIC);
unit.types().add(cls);

// Javadoc
Javadoc jc = ast.newJavadoc();
TagElement tag = ast.newTagElement();
tag.fragments().add(ast.newTextElement("Sample SWT Composite class created using the ASTParser"));
jc.tags().add(tag);
tag = ast.newTagElement();
tag.setTagName(TagElement.TAG_AUTHOR);
tag.fragments().add(ast.newSimpleName("Manoel Marques"));
jc.tags().add(tag);
cls.setJavadoc(jc);

4. 創建字段

VariableDeclarationFragment vdf = ast.newVariableDeclarationFragment();
vdf.setName(ast.newSimpleName("minimumSize"));
FieldDeclaration fd = ast.newFieldDeclaration(vdf);
fd.setModifiers(Modifier.PRIVATE);
fd.setType(ast.newSimpleType(ast.newSimpleName("Point")));
cls.bodyDeclarations().add(fd);

5. 創建構造器與語句

MethodDeclaration ctor = ast.newMethodDeclaration();
ctor.setConstructor(true);
ctor.setName(ast.newSimpleName("SampleComposite"));

// 參數
SingleVariableDeclaration param = ast.newSingleVariableDeclaration();
param.setName(ast.newSimpleName("size"));
param.setType(ast.newSimpleType(ast.newSimpleName("Point")));
ctor.parameters().add(param);

// 構造器體
Block body = ast.newBlock();
cls.bodyDeclarations().add(ctor);
ctor.setBody(body);

// 語句:GridLayout gridLayout = new GridLayout();
VariableDeclarationStatement vds = ast.newVariableDeclarationStatement(
    ast.newVariableDeclarationFragment("gridLayout"));
vds.setType(ast.newSimpleType(ast.newSimpleName("GridLayout")));
vds.getFragment().setInitializer(ast.newClassInstanceCreation("GridLayout"));
body.statements().add(vds);

// 語句:this.setLayout(gridLayout) - 使用 Assignment 方式
Assignment assign = ast.newAssignment();
assign.setLeftHandSide(ast.newSimpleName("this.setLayout"));
assign.setRightHandSide(ast.newSimpleName("gridLayout"));
body.statements().add(ast.newExpressionStatement(assign));

6. 獲取生成的源碼

public char[] getContents() {
 Document doc = new Document();
 TextEdit edits = unit.rewrite(doc, null); // 合並修改
 edits.apply(doc);
 return doc.get().toCharArray();
}

三、 編譯與運行生成的類

1. 實現 ICompilationUnit 適配編譯器

static class CompilationUnitImpl implements ICompilationUnit {
 private CompilationUnit unit;
 // 實現 getContents(), getMainTypeName(), getPackageName(), getFileName()
 // 關鍵:getContents() 同上面 rewrite 邏輯
}

2. 調用內部編譯器

Compiler compiler = new Compiler(
 new NameEnvironmentImpl(unit),
 DefaultErrorHandlingPolicies.proceedWithAllProblems(),
 new CompilerOptions(settings),
 requestor,
 new DefaultProblemFactory(Locale.getDefault())
);
compiler.compile(new ICompilationUnit[] { unit });

3. 處理編譯錯誤

for (IProblem p : requestor.getProblems()) {
 if (p.isError()) System.err.println("Error: " + p.getMessage() + " line:" + p.getSourceLineNumber());
 else if (p.isWarning()) System.out.println("Warning: " + p.getMessage());
}

4. 自定義 ClassLoader 運行

ClassLoader loader = new CustomClassLoader(getClass().getClassLoader(), requestor.getResults());
String className = unit.getPackageName() + "." + unit.getMainTypeName();
Class clazz = loader.loadClass(className);
Method main = clazz.getMethod("main", String[].class);
main.invoke(null, (Object) new String[0]);

5. CustomClassLoader 實現

static class CustomClassLoader extends ClassLoader {
 private Map<String, byte[]> classMap;
 CustomClassLoader(ClassLoader parent, List<ClassFile> classes) {
  classMap = new HashMap<>();
  for (ClassFile cf : classes) {
   classMap.put(CharOperation.toString(cf.getCompoundName()), cf.getBytes());
  }
 }
 protected Class findClass(String name) throws ClassNotFoundException {
  byte[] bytes = classMap.get(name);
  if (bytes != null) return defineClass(name, bytes, 0, bytes.length);
  return super.findClass(name);
 }
}

四、 解析現有代碼

ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setKind(ASTParser.K_COMPILATION_UNIT); // 或 K_CLASS_BODY_DECLARATIONS 等
parser.setSource(sourceString.toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(monitor); // monitor 可取消長解析

五、 AST 遍歷 - Visitor 模式

cu.accept(new ASTVisitor() {
 public boolean visit(TypeDeclaration node) {
  System.out.println("Class: " + node.getName());
  return true; // 繼續遍歷子節點
 }
 public void endVisit(MethodDeclaration node) {
  System.out.println("Method end: " + node.getName());
 }
});
  • preVisit → visit → endVisit → postVisit
  • visit() 返回 false 跳過子樹
  • 註釋節點不在樹中,需 cu.getCommentList() 單獨訪問
  • 解析錯誤:cu.getProblems() 獲取 IProblem[]

六、 進階技巧與避坑指南

  1. 節點導入:跨 AST 移動節點需 ASTNode.copySubtree(targetAST, node)
  2. 進度監控:解析大文件用 ProgressMonitorDialog 跑後台線程,UI 線程 Display.syncExec 刷新
  3. 線程安全:只讀遍歷安全,修改需 synchronize(node.getAST())
  4. ASTParser ≠ Compiler:語法合法 ≠ 語義合法 (如 Stringgg 類不存在僅編譯器能查出)
  5. 緩存優化:ICompilationUnit.getContents() 耗時,建議緩存

七、 示例項目結構

  • ASTMain:構建含 SWT 按鈕窗口的類 → 編譯 → 運行 main() 彈窗
  • ASTExplorer:三面板 (AST Tree、源碼高亮、解析錯誤),點擊樹節點高亮源碼位置

ASTExplorer 運行界面

圖 1. 運行中的 ASTExplorer:選中節點源碼藍色高亮,錯誤紅色標記

八、 總結

掌握 ASTParser 開啟了代碼生成、靜態分析、重構工具、DSL 實現等無限可能。關鍵路徑:Parser → CompilationUnit → AST 構建/遍歷 → rewrite → 編譯 → ClassLoader 運行。建議下載示例代碼邊跑邊讀,探索更多 JDT 服務。

來源:IBM developerWorks

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved