Sunday, July 24, 2005

ทดลองเขียน Hibernate XDoclet 's Eclipse Plugin #2

สืบเนื่องจากการพยายามทำ Eclipse Plugin ที่ช่วย generate
Hibernate mapping file โดยใช้ XDoclet เป็น engine ตอนที่ 1

หลังจากที่ลอง Implement Nature ไปแล้ว
ปัญหาถัดไปที่ต้อง solve ก็คือ
ทำอย่างไรจึงจะ run Doclet โดยไม่ใช้ Ant ได้
โชคดีที่ concept ของ Ant เปิดช่อง
ให้เราสามารถ run Ant task ต่างๆโดยตรงได้

โดยปกติเวลาเราเขียน ant task
สำหรับการ generate mapping file
เราจะเขียนด้วย syntax ดังนี้
<hibernatedoclet destDir="src/dao">
<fileset dir="src/dao">
<include name="mx/rd/web/model/**/*.java"/>
</fileset>

<hibernate version="2.0">
</hibernate>
</hibernatedoclet>

พอมาเขียนให้เรียกใช้จาก java ตรงๆ
ก็เลยเขียนเลียนแบบดังนี้
  
HibernateDocletTask task = new HibernateDocletTask();
task.setDestDir(new File("/tmp/xdoclet"));

HibernateSubTask subtask = new HibernateSubTask();
HibernateVersion ver = new HibernateVersion();
ver.setValue(HibernateVersion.HIBERNATE_3_0);
subtask.setVersion(ver);

task.addSubTask(subtask);

FileSet fs = new FileSet();
fs.setDir(new File("/tmp/xdoclet"));
fs.setIncludes("Role.java");

task.addFileset(fs);
task.setProject(new Project());

task.execute();

เมื่อทดลองเรียกใช้โดยตรง ก็เกิดปัญหาถัดไปว่า
ตัว Eclipse Builder ใช้ Concept Incremental Build
ซึ่งจะเลือก compile เฉพาะบาง file เท่านั้น
แต่เจ้าตัว XDoclet ใช้ concept Batch Build (เวลาทำ ทำเป็น set)
ถ้าเรา implement ตรงๆ Performance คงออกมาไม่ดีเท่าไร
เช่นสมมติว่าตัว Eclipse Builder
ระบุว่าต้องการให้ build file X.java แต่เจ้าตัว XDoclet
ดันไป scan project directory ทั้งหมด เพื่อที่จะได้เจ้าตัว
X.java มาตัวเดียว

ดังนั้นเราก็เลยพยายาม hack ต่อไป เพื่อที่จะหาว่า
จะสามารถเรียกใช้โดยตรงได้อย่างไรบ้าง
ก็ไปพบว่า มันมี method อยู่ตัวหนึ่งใน TemplateSubTask
(ซึ่งเป็น superclass ตัวหนึ่งของ HibernateSubTask)
ที่ชื่อ public void generateForClass(XClass clazz)
โชคไม่ดีที่ method นี้มี modifier เป็น protected
ทำให้เราไม่สามารถเรียกใช้ตรงๆได้
ก็เลยต้องใช้วิธีไม้ตายขั้นสุดท้าย
ก็คือเข้าไปแก้ไข source code ของ XDoclet โดยตรงเลย
โดยทำการเปลี่ยนจาก protected ให้เป็น public

Note ได้มีการทดลอง extends HibernateSubTask แล้ว override เปลี่ยน modifier ของ method generateForClass ตรงๆ แล้ว ปรากฎว่าไปเกิดปัญหาในเรื่อง namespace ของ template

เจ้า method generateForClass นี้ parameter ที่รับก็คือ XClass (อยู่ใน library ของ XJavaDoc ที่เป็นโครงสร้างพื้นฐานของ XDoclet)
สามารถหามาได้โดยวิธีนี้
    XJavaDoc xdoc = new XJavaDoc();
FileSourceSet fs = new FileSourceSet(new File("/tmp/xdoclet"));
xdoc.addSourceSet(fs);
XClass cls = xdoc.getXClass("mx.Role");


หลังจากทดลอง run ก็พบว่า ยังมี property หลายตัว
ที่ยังไม่ถูก initialize (เนื่องจากเราไม่ได้ run จากช่องทางปกติ)
ก็เลยต้อง initialize เพิ่มเติมดังนี้
    HibernateSubTask sbt = new HibernateSubTask();      

DocletContext ctx = new DocletContext("/tmp/gen",
null, null, new SubTask[] {sbt},
new Hashtable(), new HashMap(), false, false, null);
DocletContext.setSingleInstance(ctx);
ctx.setActiveSubTask(sbt);

ModuleFinder.initClasspath(getClass());
sbt.init(xdoc);


หลังจากเรียก initialize property ต่างๆแล้ว
ก็สามารถเรียก generate โดยตรงได้ดังนี้
    sbt.generateForClass(cls);

Related link from Roti

No comments: