Friday, April 08, 2005

Java SciMark 2.0

สำหรับผู้ที่ชื่นชอบตัวเลข
Java SciMark 2.0: "How fast is your Java platform for number crunching?"

ของผมเครื่อง pentium 2.6 jdk1.5_01, linux ubuntu 5.04
ถ้าเป็น applet ใน browser
211.6, 118.5, 354.3, 44.4, 136.1, 404.4
ส่วนถ้าใช้ appletviewer
206.2 95.5 356.3 44.4 136.4 398.5

แต่ถ้า switch ไปที่ windows xp, jdk1.5.0_01
190.7, 97.6, 348.5, 44.3, 136.8, 326.1

ลองเปลี่ยนไปใช้ jdk1.4.2
203.4, 79.4, 364.4, 46.5, 135.2, 391.7

? ไม่เห็นต่างกันเลย ก็ไล่ๆกันหมด
แสดงว่ามันไปขึ้นอยู่กับ cpu factor อย่างเดียวหรือเปล่า

Related link from Roti

Cocoon Control Flow

ใน Web-Application ทีมีลักษณะเป็น control-flow เข่น สมมติว่า user เริ่มต้นป้อนที่หน้าจอ A ถ้าเขาเลือก tick ถูกจะ flow ต่อไปหน้าจอ B แต่ถ้าเขา tick ผิด ก็จะ flow ต่อไปหน้าจอ C ...
ในการที่จะ solve ปํญหานี้ เราจะต้อง keep track state ของ userไว้ (ไม่ว่าจะไว้ใน cookie หรือใน session)
และเมื่อเกิด request ก็ต้องตัดสินว่าควรจะ flow ไป state ไหนต่อ โดยก่อนไปก็อาจจะมีการทำ action บางอย่างก่อน และหลังจากที่ไปถึง state นั้นแล้ว จะต้อง present view ที่เหมาะสมให้กับ user

ใครที่เคยเขียนโปรแกรมสำหรับปัญหาประเภทนี้ คงจะรู้ว่า ตัวโปรแกรมจะมีลักษณะกระจัดกระจาย ทำให้ยากต่อการมองดูภาพรวม เช่นจะดูว่าอะไรจะเกิดถัดไป หรือก่อนมาถึงจุดนี้มัน flow มาจากจุดไหน
ยิ่งโปรแกรมที่มี flow เปลี่ยนแปลงไปเรื่อยๆ (requirement changed) หรือมี flow ที่ซับซ้อน การที่จะมานั่งไล่ดูโปรแกรมถือว่าเรื่องสาหัสเอาการ

Cocoon เป็น Web-Application Framework ตัวหนึ่งของค่าย apache ซึ่งมี concept เป็นแบบ pipe line trasformation
มี feature หนึ่งของ Cocoon ที่น่าสนใจก็คือ Control Flow
ซึ่งเสนอทางเลือกในการแก้ไขปัญหาแบบนี้ โดยแทนที่จะ solve flow โดยใช้ finite state machine
ก็เปลี่ยนมาเป็นการเขียนโปรแกรม ซึ่ง apply เอา concept ของ Contiuations มาใช้

สมมติว่าเราจะเขียนโปรแกรมเครื่องคิดเลข โดยโปรแกรมแสดง page1 ให้ user เลือกเลขที่ต้องการ จากนั้นแสดง page ให้ user เลือกเลขถัดไป จากนั้นแสดง page3 เพื่อให้เลือก opertaor ที่ต้องการ เมื่อครบแล้วโปรแกรมก็แสดงผลลัพท์ให้ดูโดยผ่าน page4
ในการ solve ตัวอย่างนี้ Cocoon จะใช้วิธีเขียนดังนี้
function calculator()
{
var a, b, operator;

cocoon.sendPageAndWait("getA.html");
a = cocoon.request.get("a");

cocoon.sendPageAndWait("getB.html");
b = cocoon.request.get("b");

cocoon.sendPageAndWait("getOperator.html");
operator = cocoon.request.get("op");

try {
if (operator == "plus")
cocoon.sendPage("result.html", {result: a + b});
else if (operator == "minus")
cocoon.sendPage("result.html", {result: a - b});
else if (operator == "multiply")
cocoon.sendPage("result.html", {result: a * b});
else if (operator == "divide")
cocoon.sendPage("result.html", {result: a / b});
else
cocoon.sendPage("invalidOperator.html", {operator: operator});
}
catch (exception) {
cocoon.sendPage("error.html", {message: "Operation failed: " + exception.toString()});
}
}

จะเห็นว่าการเขียนโปรแกรมในลักษณะนี้ จะง่ายต่อการทำความเข้าใจกว่ามาก โดย Cocoon จะใช้ javascript เข้ามาเป็นตัว control flow (javascript นี้จะ run อยู่ในฝั่ง server) กรณีที่ user สั่ง cocoon.sendPageAndWait ตัว cocoon จะทำการ suspend process นี้ (เรียกว่า continuations object ) และทำการ put เก็บไว้ใน map โดยใช้ unique id ค่าหนึ่ง โดย id นี้จะถูกส่งกลับไปกับ response page ด้วย และเมื่อ request วิ่งกลับมาอีกครั้ง ก็จะทำการ get process (โดยใช้ id ที่อยู่ใน request page นั้น) ขึ้นมา run ต่อ

นอกจากนี้ วิธีการนี้ยังช่วยแก้ปัญหากรณี user กด back หรือ user clone browser window ใด้อีกด้วย เพราะในแต่ละขั้นของ sendPageAndWait จะเกิด new Continuations object เสมอ และตัว Id ของ object นี้จะถูกฝังไว้ใน page ดังนั้นการที่ user กด back และ submit เข้ามาใหม่ ก็เป็นแต่เพียงการย้อนกลับไปใช้ continuations object ตัวเก่าเท่านั้นเอง

Note: อ่านเพิ่มเติม feature นี้ได้จาก

Related link from Roti

Wednesday, April 06, 2005

ใช้ commons.lang ช่วยเขียน hashCode, equals Method

ใครที่เคยเขียน Java Object จะพบว่า
ในบาง use case จำเป็นต้องมีการ overide equals กับ hashCode method
(ปกติใน object ที่เราไม่ได้ implement equals method
ความหมายของ equals ก็คือดูว่าเป็น instance เดียวกันหรือไม่
แต่ถ้าเราต้องการเปลี่ยนความหมายของ equals
ให้เป็นในลักษณะว่า property ทุกๆตัวเท่ากันหรือไม่แทน
ก็ต้อง override equals method ซึ่ง java object มี
constrain ว่าถ้าเรา overide equals method เมื่อไร
ก็ควรจะต้อง override hashCode method ด้วย
)

การเขียน method 2 อันนี้เราสามารถใช้
jakarta-commons-lang เข้ามาช่วยได้ โดยใช้ class
  • HashCodeBuilder
  • EqualsBuilder
ตัวอย่างการใช้งานกรณี hashCode
public class Person {
String name;
int age;
boolean isSmoker;
...

public int hashCode() {
// you pick a hard-coded, randomly chosen, non-zero, odd number
// ideally different for each class
return new HashCodeBuilder(17, 37).
append(name).
append(age).
append(smoker).
toHashCode();
}
}

หรือจะใช้ reflection แทนก็ได้
  public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}

ส่วนกรณี equals ก็เช่นเดียวกัน
  public boolean equals(Object o) {
if ( !(o instanceof MyClass) ) {
return false;
}
MyClass rhs = (MyClass) o;
return new EqualsBuilder()
.appendSuper(super.equals(o))
.append(field1, rhs.field1)
.append(field2, rhs.field2)
.append(field3, rhs.field3)
.isEquals();
}
ถ้าใช้ reflection
  public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}

ส่วนใครที่ใช้ Eclipse ถ้าอยากให้ชีวิตง่ายขึ้นกว่านี้อีก
ก็ต้องใช้ Commonclipse ซึ่งมี plugin ที่ช่วย generate
method ให้อีกที (ใช้ common lang builder เหมือนกัน)

Note: กรณีที่ใช้ hibernate มักจะมี
ข้อสงสัยว่าเราควรจะ override equals และ hashCode หรือไม่
ในกรณี use case ปกติ เราไม่จำเป็นต้อง override equals หรือ hashCode
เราจะ override ต่อเมื่อ
- it is a used as a composite primary key class
- instances of that class, loaded in different Hibernate Sessions, are in the same Set (or should be compared)
ส่วนรูปแบบการ implement ปกติจะใช้
    public boolean equals(Object other) {
if ( !(other instanceof Client) ) return false;
Client castOther = (Client) other;
return new EqualsBuilder()
.append(this.getId(), castOther.getId())
.isEquals();
}
// Note: id is object id (surrogate identifier) of this object

public int hashCode() {
return new HashCodeBuilder()
.append(getId())
.toHashCode();
}

สถานะการณ์จะยุ่งยากขึ้นอีกถ้ามีการ compare proxy object กับ real object
อ่านเพิ่มเติมการถกเถึยงประเด็นเหล่านี้ได้ที่ this forum

Related link from Roti

Tuesday, April 05, 2005

Classifier4J

Classifier4J เป็น opensource สำหรับทำ Text Classification
มองหามานานแล้ว พึงเจอ project นี้เป็นตัวแรกที่เป็น opensource
ก็เลย load มาทดลองดู

ใน core package จะมี Classifier ให้เราใช้ 2 แบบก็คือ
ผมทดลอง download ตัว source มา compile เอง
โดยใช้ maven เป็นตัว build
ลำดับการ build เท่าที่ลองมั่วดูก็คือ
ต้อง build core ก่อนโดย cd core แล้วสั่ง maven jar:install
กรณีที่ต้องการ build optional ด้วยนั้นจะติดปัญหาว่า
ในส่วน dependency file ชี้ไปยัง repository name ที่ไม่ถูกต้อง
ให้ทำการแก้ไข project.xml ใน optional directory เสียก่อน
โดยเปลี่ยนส่วน dependency จากของเดิมให้เป็นตามนี้
  <dependency>
<groupId>classifier4j</groupId>
<artifactId>Classifier4J</artifactId>
<version>0.6</version>
</dependency>

จากนั้นก็สั่ง maven jar:install เช่นเดียวกัน

จากการทดลองเล่นดู พบว่ามีปัญหากับภาษาไทย
ก็เลยไล่ code ดูพบว่า ตัว DefaultTokenizer ที่ให้มา
นั้น implment ง่ายๆโดยใช้ method split ของ String Class
ซึ่งไม่สามารถตัดคำไทยได้แน่นอน
ก็เลยเขียน Tokenizer ที่ใช้ BreakIterator ขึ้นมา
public class BreakTokenizer implements ITokenizer {
public String[] tokenize(String input) {
ArrayList list = new ArrayList();
BreakIterator bt = BreakIterator.getWordInstance();
bt.setText(input);
int start = bt. first ();
for (int end = bt. next (); end != BreakIterator. DONE; start = end, end = bt. next ()) {
String tmp = input.substring(start, end);
list.add(tmp);
}
return (String[]) list.toArray(new String[0]);
}
}


นอกจากนี้ยังพบว่า VectorClassifier
นั้นยังเขียนไม่เรียบร้อยนัก ขาด method หรือ constructor
สำหรับ set custom Tokenizer ไปก็เลย แก้ code
เพิ่ม method setTokenizer แล้วก็สั่ง build ใหม่

ตัว code ที่ทดลองใช้ง่ายๆ (ยังไม่มีเรื่อง stopword ไทย)
มีตัวอย่างดังนี้
 public static void main(String[] args) throws ClassifierException {
TermVectorStorage store = new HashMapTermVectorStorage();
VectorClassifier cs = new VectorClassifier(store);
BreakTokenizer tk = new BreakTokenizer();
cs.setTokenizer(tk);

cs.teachMatch("สวัสดีครับนี่คือภาษาไทย");
System.out.println(cs.classify("สวัสดี ภาษาไทย"));

IWordsDataSource ds = new SimpleWordsDataSource();
BayesianClassifier bcs = new BayesianClassifier(ds, tk);
bcs.teachMatch("สวัสดีครับนี่คือภาษาไทย");
System.out.println(bcs.classify("สวัสดี ภาษาไทย"));
}


ผลลัพท์ที่ได้จากการ run
0.7071067811865476
0.99

Related link from Roti

Monday, April 04, 2005

Free (and powerfull) Window Installer

เมื่อก่อนเคยใช้อยู่ตัวหนึ่ง(จำชื่อไม่ได้แล้ว)
ตอนนั้นใช้สำหรับลงโปรแกรมที่เขียนด้วย java+Swing
การใช้ก็ตรงไปตรงมา ระบุ file, directory
ทีติดตั้ง แล้วก็ customize text หรือ icon

พึ่งมาเจอตัวนี้ NSIS: Home
เข้าท่าดีตรงที่เราเขียนเป็น script แทน
ว่าจะ install อย่างไร
ซึ่งทำให้เราเขียน installer ที่ซับซ้อนได้

ตัวอย่าง script
ตัวอย่าง script ที่ install web application บน tomcat
register conduits with the Palm HotSync Manager
ตัวอย่างการตรวจ Java Runtime และ automatic download
Install Java Web Start Application (offline install)

Related link from Roti

Windows Installer สำหรับ Eclipse RCP

เป็น plugin ที่ช่วยสร้าง window installer file
สำหรับ RCP (Rich Client Platform) Product
มี Free Edition ที่สามารถ install โดยใช้ Software Updates Feature
1. Select the menu "Help > Software Updates > Find and Install".
2. Choose "Search for new features to install"
3. Click "New Remote Site", and enter the XtremeJ update site URL "http://www.xtremej.com/updates/"
4. Select the "XtremeJ RCP Builder Feature" and proceed to finish the installation.

ดูคู่มือได้ที่ XtremeJ RCP Builder User's Guide

Related link from Roti

การใช้ Eclipse Compiler (JDT) ใน Ant

เราสามารถใช้ java compiler ของ Eclipse ใน ant build file ได้
โดยผ่านชองทางที่ ant กำหนด นั่นก็คือ property build.compiler
ตัวอย่าง
<project default="compile">

<target name="init" if="eclipse.running">
<property name="build.compiler"
value="org.eclipse.jdt.core.JDTCompilerAdapter" />
</target>

<target name="compile" depends="init">
<echo>debug build.compiler = ${build.compiler}</echo>
<javac srcdir="." destdir="./target" />
</target>

</project>

จากตัวอย่างเราจะใช้ property "eclipse.running"
เป็นตัวตรวจสอบว่า run อยู่ภายใต้ eclipse หรือไม่
ถ้าใช่จึงจะใช้ JDT

Note: ข้อพึงระวังก็คือในตอนที่ run ant ใน Eclipse
ให้กำหนด option Runtime jre เป็น "Run in the same JRE..." ด้วย

Related link from Roti