Thursday, November 23, 2006

Eclipse JDT, ASTParser

ปัจจัยหนึ่งที่ทำให้ Eclipse ได้เปรียบ netbeans (หมายถึง netbeans ตัวเก่าๆนะ ตัวใหม่นี่ยังไม่มีข้อมูล)
ก็คือตัว Java Parser
ซึ่ง bundle อยู่ใน package eclipse JDT (java development tools)

เมื่อใดก็ตามที่เราเปิด source code ขึ้นมาใน editor
eclipse จะใช้ ASTParser ทำการ parse source code
ไปเป็น AST (Abstract Syntax Tree)
ข้อมูล AST ที่ได้จะถูกนำไปใช้หลายๆที่ เช่นใช้ใน Outline view
แต่ที่หนึ่งที่สำคัญก็คือ มันใช้เป็นข้อมูลสำหรับการทำ Refactoring

ลองดู code ตัวอย่างนี้
สมมติเรามี

int x;
x = 2 * 3;

ถ้าเราต้องการ refactor ยุบให้เหลือแค่นี้
int x = 2 * 3;


ผมจะลองเขียน code ให้ดูว่า ถ้าใช้ Eclipse JDT api
เข้ามายุบ source code ข้างบนนั้น
เราจะต้องเขียนอย่างไรบ้าง
เริ่มด้วยการ parse java code ก่อน

ASTParser parser = ASTParser.newParser(AST.JLS3);
parser.setKind(ASTParser.K_STATEMENTS);
String src = "int x; x= 2 * 3;";
parser.setSource(src.toCharArray());
ASTNode node = parser.createAST(null);

AST.JLS3 คือ version ของ java code ที่เราจะ parse
ตัว ASTParser มี choice ให้เลือกอยู่แค่ 2 ตัวคือ JLS3 กับ JLS2
ตัว JLS3 จะ support syntax ของ Java 1.5

ขั้นถัดไปก็คือทำการหาว่า จุดไหนที่ทำ refactor ได้บ้าง
โดยเราจะค้นหา statement ที่เป็น variable declaration
กับ statement ที่เป็น assignment
การค้นหานี้ เราจะใช้ visitor pattern ในการค้นหา
(Note: เขียนแบบสั้นๆ พอให้ทำงานได้ ไม่เช็คเงื่อนไขใดๆทั้งสิ้น)

final ArrayList list = new ArrayList();
final HashMap map = new HashMap();

node.accept(new ASTVisitor() {
@SuppressWarnings("unchecked")
public boolean visit(VariableDeclarationStatement node) {
list.add(node);
return false;
}

public boolean visit(Assignment assignment) {
Expression left = assignment.getLeftHandSide();
map.put(left.toString(), assignment);
return false;
}
});

ขั้นสุดท้ายก็คือการแก้ไข AST ให้ตรงตามที่เราต้องการ
// loop ไปตาม declaration statement ที่หามาได้
for (Iterator iter = list.iterator(); iter.hasNext();) {

VariableDeclarationStatement stmt = (VariableDeclarationStatement) iter.next();
// เนื่องจาก declarable statement หนึ่งๆ
// มีได้หลายตัวแปรพร้อมๆกัน
// ดังนั้นเราต้อง loop ไปตามตัวแปรแต่ละตัวอีกที
List fragment = stmt.fragments();
for (Iterator iterator = fragment.iterator(); iterator.hasNext();) {
VariableDeclarationFragment f = (VariableDeclarationFragment) iterator.next();
//ทำเฉพาะ case ที่ตัวแปรไม่ได้มีการ initlize ไว้
if (f.getInitializer() == null) {
// ค้นหา assigment ที่มีตัวแปรเดียวกัน
Assignment assignment = (Assignment) map.get(f.getName().toString());
Expression right = assignment.getRightHandSide();
f.setInitializer(copyExpression(right));
assignment.getParent().delete();
}
}
}

จะเห็นว่า เราแค่ copy right hand side ของ Assignment statement
มาใส่ตรง Initialize value ของ Variable declaration Statement
จากนั้นก็ลบ assignment statement ทิ้งเสีย

สนใจรายละเอียด
ลองตามไปอ่าน Tutorial ของ Eclipse ที่ชื่อ Abstract Syntax Tree

Related link from Roti

2 comments:

iporsut said...

พี่ป๊อกครับ ตรง copyExpression นี่เอามาจากไหนเหรอครับ ผมลองทำตามแล้วติดตรงนี้

iporsut said...

ทำได้แล้วครับ ผมไปใช้
f.setInitializer((Expression)ASTNode.copySubtree(f.getAST(), right));

แบบนี้แทน