เป็น project ที่ทดลองค้นหาเพลงโดย
ใช้การเคาะจังหวะเป็นข้อมูล input
ผมทดลองแล้ว 4 เพลง ตรงเพี๊ยะเสีย 2 เพลง
ที่ค้นได้ก็มี 10 little indian กับ twinkle little start
ส่วน happy birth day เคาะยังไงก็หาไม่เจอ
ลองได้ที่นี่ครับ link
Saturday, May 21, 2005
SiteMesh
หลังจากลองไล่ดู code ที่ AppFuse generate ให้
ก็พบว่ามีการนำ SiteMesh มาใช้
เจ้า sitemesh ก็คือ decoration framework (ทำหน้าที่เหมือน Tiles ใน Struts)
ทำหน้าที่ intercept response จาก web application
แล้วทำการ extract เฉพาะส่วน body, head
เพื่อนำไปแสดงบน template ที่ได้เตรียมไว้
อธิบายยากแท้ ดูรูปเอาแล้วกัน

เท่าที่อ่านดู feature หรือ การใช้งาน
ก็ดูดี คงต้องทดลองใช้งานก่อน
จึงจะรู้ว่าดีกว่า Tiles หรือเปล่า
แต่เท่าที่จำได้เวลาใช้ tiles จะน่าเบื่อ
ตอนเขียน xml definition ของ tiles
ซึ่งซ้ำไปซ้ำมา และมี Naming ของ page
เกิดขึ้นอีก 1 กลุ่ม (ต้องตั้งชื่อให้กับ
ผลลัพท์ของการจับคู่ template กับ content)
feature ของ sitemesh
ในการ config ใช้ sitemesh ก็เพียงแต่ใส่ filter, filter-mapping เข้าไปใน web.xml
เริ่มแรกก็สร้าง file sitemesh.xml ไว้ใต้ WEB-INF
parser tag เป็นตัวกำหนดว่าจะใช้ parser ตัวไหนสำหรับ content แต่ละแบบ
แต่เจ้า sitemesh ให้มาเฉพาะ html parser เท่านั้น
ส่วน decorator-mapper เป็นตัวกำหนด
ว่าจะใช้ decorator อะไรสำหรับเงื่อนไขแบบไหน
ในกรณีนี้เป็นการใช้ ConfigDecoratorMapper
ซึ่งจะอ้างถึง config file ที่ชื่อ decorators.xml อีกที
ในเนื้อหาของ file decorators.xml
เราสามารถกำหนดได้ว่า จะให้ exclude อะไร
แล้วก็กำหนด pattern ว่า pattern อะไร
ใช้ jsp ตัวไหนเป็น template
สุดท้ายก็เป็นตัวอย่างของ template
ซึ่งใช้ taglib decorator:head กับ decorator:body
เป็นตัวกำหนดจุดที่จะนำเนื้อหามา insert
ข้อมูลเพิ่มเติม
ก็พบว่ามีการนำ SiteMesh มาใช้
เจ้า sitemesh ก็คือ decoration framework (ทำหน้าที่เหมือน Tiles ใน Struts)
ทำหน้าที่ intercept response จาก web application
แล้วทำการ extract เฉพาะส่วน body, head
เพื่อนำไปแสดงบน template ที่ได้เตรียมไว้
อธิบายยากแท้ ดูรูปเอาแล้วกัน
เท่าที่อ่านดู feature หรือ การใช้งาน
ก็ดูดี คงต้องทดลองใช้งานก่อน
จึงจะรู้ว่าดีกว่า Tiles หรือเปล่า
แต่เท่าที่จำได้เวลาใช้ tiles จะน่าเบื่อ
ตอนเขียน xml definition ของ tiles
ซึ่งซ้ำไปซ้ำมา และมี Naming ของ page
เกิดขึ้นอีก 1 กลุ่ม (ต้องตั้งชื่อให้กับ
ผลลัพท์ของการจับคู่ template กับ content)
feature ของ sitemesh
- Determine decorator based on path of requested page.
- Use different decorators based on time, locale or browser.
- Use simplified decorators for search-engine robots.
- Switch decorators based on a URL parameter, request attribute or meta-tag.
- Use custom decorators based on user's saved settings...
ในการ config ใช้ sitemesh ก็เพียงแต่ใส่ filter, filter-mapping เข้าไปใน web.xml
<filter>
<filter-name>sitemesh</filter-name>
<filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
<!-- These are needed by Tomcat 5 for forwards -->
<!--dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher-->
</filter-mapping>
เริ่มแรกก็สร้าง file sitemesh.xml ไว้ใต้ WEB-INF
parser tag เป็นตัวกำหนดว่าจะใช้ parser ตัวไหนสำหรับ content แต่ละแบบ
แต่เจ้า sitemesh ให้มาเฉพาะ html parser เท่านั้น
ส่วน decorator-mapper เป็นตัวกำหนด
ว่าจะใช้ decorator อะไรสำหรับเงื่อนไขแบบไหน
ในกรณีนี้เป็นการใช้ ConfigDecoratorMapper
ซึ่งจะอ้างถึง config file ที่ชื่อ decorators.xml อีกที
<sitemesh>
<property name="decorators-file" value="/WEB-INF/decorators.xml"/>
<excludes file="${decorators-file}"/>
<page-parsers>
<parser default="true" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser"/>
<parser content-type="text/html" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser"/>
<parser content-type="text/html;charset=ISO-8859-1" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser"/>
</page-parsers>
<decorator-mappers>
<mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
<param name="config" value="${decorators-file}"/>
</mapper>
</decorator-mappers>
</sitemesh>
ในเนื้อหาของ file decorators.xml
เราสามารถกำหนดได้ว่า จะให้ exclude อะไร
แล้วก็กำหนด pattern ว่า pattern อะไร
ใช้ jsp ตัวไหนเป็น template
<decorators defaultdir="/decorators">
<excludes>
<pattern>/demos/*</pattern>
<pattern>/resources/*</pattern>
</excludes>
<decorator name="default" page="default.jsp">
<pattern>/*</pattern>
</decorator>
</decorators>
สุดท้ายก็เป็นตัวอย่างของ template
ซึ่งใช้ taglib decorator:head กับ decorator:body
เป็นตัวกำหนดจุดที่จะนำเนื้อหามา insert
<%@ taglib
uri="http://www.opensymphony.com/sitemesh/decorator"
prefix="decorator" %>
<html>
<head>
<title>
My Site -
<decorator:title default="Welcome!" />
</title>
<decorator:head />
</head>
<body>
<table>
<tr>
<td>
<H1>
SiteMesh Corporation
<H1>
</td>
</tr>
<tr>
<td><decorator:body /></td>
</tr>
<tr>
<td> SiteMesh copyright</td>
</tr>
</table>
</body>
</html>
ข้อมูลเพิ่มเติม
Related link from Roti
Thursday, May 19, 2005
Eclipse 3.0 stat
มีใตรเคยเห็น stat นี้หรือยังครับ
ตัวที่ผมชอบก็มี
Wow เยี่ยม เคยทำ test แค่หลัก 20-100
ของเราอาจจะใช้จำนวนซองมาม่า แทน
เคยทำไว้ได้สูงเหมือนกัน แต่จำตัวเลขไม่ได้
แต่เป็นเพราะออกแบบผิด
ทำเป็น build script เดียว แทน
ที่จะแยกไปตาม subproject
ทุกคนเลยรุมแก้ที่จุดเดียว
โปรเจคที่แล้ว 4 เดือนครึ่ง 200,000 กว่าบรรทัด
เทียบกันไม่ติด
ตัวที่ผมชอบก็มี
Automated JUnit tests run every build: 21,332
Wow เยี่ยม เคยทำ test แค่หลัก 20-100
Donuts consumed (North American committers): 1,120
Cigarettes inhaled (European committers): 14,656
ของเราอาจจะใช้จำนวนซองมาม่า แทน
Most CVS revisions for one file (JavaCore.java): 413
เคยทำไว้ได้สูงเหมือนกัน แต่จำตัวเลขไม่ได้
แต่เป็นเพราะออกแบบผิด
ทำเป็น build script เดียว แทน
ที่จะแยกไปตาม subproject
ทุกคนเลยรุมแก้ที่จุดเดียว
Lines of Java source code: 1,903,219
โปรเจคที่แล้ว 4 เดือนครึ่ง 200,000 กว่าบรรทัด
เทียบกันไม่ติด
Related link from Roti
Dumbster - Email Testing
ในการเขียน Application ในยุค Internet นี้
ส่วนใหญ่เรามักจะออกแบบให้มี feature
Mail Alert ซึ่งเป็นการแจ้งเตือนผู้ใช้
ถ้าเกิดเหตุการณ์อะไรบางอย่าง
สมัยก่อนเวลาผม implement feature นี้
แล้วเกิดต้องการจะ test
ก็เพียงแต่ set config ให้ mail ไปหา
account ตัวเอง แล้วก็ทดลองจำลอง
เหตุการณ์ จากนั้นก็เข้าไป check mail
ตัวเองว่ามีการส่ง mail มาจริงหรือเปล่า
มี solution อีกอันที่ผมพบใน AppFuse
ก็คือใช้ Dumbster ซึ่งเป็น Fake SMTP Server
ร่วมกับ junit ในการ test
ตัวอย่างการเขียน testcase
กรณีที่ test แบบนี้ เราจะเรียตัว dumbster ว่าเป็น
Mock Object (จริงๆอาจจะเรียกได้ไม่เต็มปากนัก
แต่ลักษณะ pattern เป็นไปตามนั้น)
สำหรับใครที่ยังไม่คุ้นกับการ
test แบบนี้ ลองอ่านเรื่อง Mock object ที่ link นี้ครับ
What Are Mock Objects?
MockObjects
ไว้คราวหน้าจะแสดงให้ดูว่า Tapestry
มีวิธีการใช้ MockObjects ในการ test อย่างไร
(ได้ตัวอย่างมาจาก AppFuse อีกเช่นกัน)
ส่วนใหญ่เรามักจะออกแบบให้มี feature
Mail Alert ซึ่งเป็นการแจ้งเตือนผู้ใช้
ถ้าเกิดเหตุการณ์อะไรบางอย่าง
สมัยก่อนเวลาผม implement feature นี้
แล้วเกิดต้องการจะ test
ก็เพียงแต่ set config ให้ mail ไปหา
account ตัวเอง แล้วก็ทดลองจำลอง
เหตุการณ์ จากนั้นก็เข้าไป check mail
ตัวเองว่ามีการส่ง mail มาจริงหรือเปล่า
มี solution อีกอันที่ผมพบใน AppFuse
ก็คือใช้ Dumbster ซึ่งเป็น Fake SMTP Server
ร่วมกับ junit ในการ test
ตัวอย่างการเขียน testcase
public void testExecute() throws Exception {
request.addParameter("username", "tomcat");
page.setRequestCycle(getCycle(request, response));
SimpleSmtpServer server = SimpleSmtpServer.start(2525);
page.execute(page.getRequestCycle());
assertFalse(page.hasErrors());
// verify an account information e-mail was sent
server.stop();
assertTrue(server.getReceivedEmailSize() == 1);
// verify that success messages are in the request
assertNotNull(page.getSession().getAttribute("message"));
}กรณีที่ test แบบนี้ เราจะเรียตัว dumbster ว่าเป็น
Mock Object (จริงๆอาจจะเรียกได้ไม่เต็มปากนัก
แต่ลักษณะ pattern เป็นไปตามนั้น)
สำหรับใครที่ยังไม่คุ้นกับการ
test แบบนี้ ลองอ่านเรื่อง Mock object ที่ link นี้ครับ
What Are Mock Objects?
MockObjects
ไว้คราวหน้าจะแสดงให้ดูว่า Tapestry
มีวิธีการใช้ MockObjects ในการ test อย่างไร
(ได้ตัวอย่างมาจาก AppFuse อีกเช่นกัน)
Related link from Roti
Wednesday, May 18, 2005
Rounded, shaded corner box with minimum div tag
ปกติเวลาเราทำ rounded box ด้วย css จะพบว่า
ตัว content ที่เราใช้ถึงแม้จะเอาส่วน presentation ออกไปแล้ว
ก็ยังอุดมไปด้วย div block อยู่ดี (แถมยังไม่ค่อยสื่อความหมายเท่าไรด้วย)
ตัวอย่าง
มีอีก idea หนึ่งก็คือเปลี่ยนไปใช้ javascript
ให้เป็นคน insert tag พวกนี้แทน
จากตัวอย่างข้างบน ก็จะลดรูปเหลือแค่
จากนั้นเมื่อเวลาที่ browser load ไป
ตัว javascript ก็จะจัดการแทรก div tag
เพิ่มเติมให้เรา
อย่างนี้สิ ค่อยดู simple หน่อย (เฉพาะใน
content)
ดูบทความที่พูดถึงเรื่องนี้ที่
Transparent custom corners and borders
ตัว content ที่เราใช้ถึงแม้จะเอาส่วน presentation ออกไปแล้ว
ก็ยังอุดมไปด้วย div block อยู่ดี (แถมยังไม่ค่อยสื่อความหมายเท่าไรด้วย)
ตัวอย่าง
<div class="cb">
<div class="bt"><div></div></div>
<div class="i1">
<div class="i2">
<h1>title go here</h1>
<p>xxxxx</p>
</div>
<div class="bb"><div></div></div>
</div>
มีอีก idea หนึ่งก็คือเปลี่ยนไปใช้ javascript
ให้เป็นคน insert tag พวกนี้แทน
จากตัวอย่างข้างบน ก็จะลดรูปเหลือแค่
<div class="cbb">
<h1>title go here</h1>
<p>xxxx</p>
</div>
จากนั้นเมื่อเวลาที่ browser load ไป
ตัว javascript ก็จะจัดการแทรก div tag
เพิ่มเติมให้เรา
อย่างนี้สิ ค่อยดู simple หน่อย (เฉพาะใน
content)
ดูบทความที่พูดถึงเรื่องนี้ที่
Transparent custom corners and borders
Related link from Roti
Tuesday, May 17, 2005
DBUnit
ต่อจากเมื่อวานที่ได้ทดลองใช้ AppFuse ไป
จากการอ่าน tutorial ทำให้รู้ว่า AppFuse ใช้
DBUnit เป็นตัว setup database สำหรับ
การ run testcase
สมัยก่อนผมใช้ ant task ที่ชื่อ <sql>
ในการ setup database ก่อนที่จะทำ
test case โดยใช้ผ่าน ant ดังนี้
ในส่วนของ DBUnit ก็สามารถใช้ผ่าน ant เหมือนกัน
(หรืออาจะใช้ api ตรงๆใน testcase ก็ได้)
โดยมีวิธีใช้ดังนี้
สิ่งที่แตกต่างกันก็คือถ้าเราใช้ sql task
เราจะต้องเตรียม data ด้วยคำสั่ง sql
ส่วนใน dbunit สามารถเตรียม dataset ใน
format xml ได้
กรณี fomat XmlDataSet
กรณี format FlatXmlDataSet
สังเกตุว่าใน DBUnit Task จะมี Attribute operation
โดยเราสามารถระบุ operation ได้ดังนี้
จากการอ่าน tutorial ทำให้รู้ว่า AppFuse ใช้
DBUnit เป็นตัว setup database สำหรับ
การ run testcase
สมัยก่อนผมใช้ ant task ที่ชื่อ <sql>
ในการ setup database ก่อนที่จะทำ
test case โดยใช้ผ่าน ant ดังนี้
<sql
driver="org.postgresql.Driver"
url="jdbc:postgresql://${test.host.address}:${test.port}/${test.database}?charSet=MS874"
userid="postgres"
password="xxxxx"
autocommit="true"
classpathref="maven.dependency.classpath"
onerror="continue"
encoding="MS874"
>
<fileset dir="src/test/sql">
<include name="serverSetup.sql"/>
</fileset>
</sql>
ในส่วนของ DBUnit ก็สามารถใช้ผ่าน ant เหมือนกัน
(หรืออาจะใช้ api ตรงๆใน testcase ก็ได้)
โดยมีวิธีใช้ดังนี้
<dbunit driver="${database.driver_class}"
supportBatchStatement="false"
url="${database.url}"
userid="${database.username}"
password="${database.password}">
<operation type="${operation}" src="${file}" format="xml"/>
</dbunit>
สิ่งที่แตกต่างกันก็คือถ้าเราใช้ sql task
เราจะต้องเตรียม data ด้วยคำสั่ง sql
ส่วนใน dbunit สามารถเตรียม dataset ใน
format xml ได้
กรณี fomat XmlDataSet
<?xml version="1.0" encoding="utf-8"?>
<dataset>
<table name='user_role'>
<column>username</column>
<column>role_name</column>
<row>
<value>tomcat</value>
<value>tomcat</value>
</row>
<row>
<value>mraible</value>
<value>admin</value>
</row>
</table>
</dataset>
กรณี format FlatXmlDataSet
<!DOCTYPE dataset SYSTEM "my-dataset.dtd">
<dataset>
<user_role username="tomcat"
role_name="tomcat"/>
</dataset>
สังเกตุว่าใน DBUnit Task จะมี Attribute operation
โดยเราสามารถระบุ operation ได้ดังนี้
- UPDATE DBUnit จะตั้ง assumption ว่ามี data อยู่ใน Database แล้ว
โดยพยายามจะนำ dataset ที่เตรียมไว้ไป update - INSERT ใช้ assumption ว่า ยังไม่มี table อยู่ใน Database
- DELETE DBUnit จะ delete ข้อมูลใน table เฉพาะ
ที่อยู่ใน dataset - DELETE_ALL delete ข้อมูลทุก row
สำหรับ table ที่มีชื่ออยู่ใน DataSet - REFRESH ถ้า data มีอยู่แล้วใช้ update
แต่ถ้ายังไม่มีใช้ insert - CLEAN_INSERT DBUnit จะ delete all
ก่อนที่จะ insert ข้อมูลเข้าไป
Related link from Roti
Monday, May 16, 2005
ลองใช้ AppFuse
ติดตาม blog ของ Raible Design มาได้พักใหญ่แล้ว
ได้ยินเจ้า AppFuse มาหลายเดือนแล้ว ยังไม่ได้ลองสักที
พอดีมีโปรเจคใหม่อันหนึ่งเป็นโปรเจคเล็กๆ แค่แสดงความ
เป็นไปได้เฉยๆ ก็เลยวางแผนว่าจะใช้
Hibernate + Spring + Tapestry
แต่ปัญหาของโปรเจคใหม่ ก็คือ การ setup environment
ในการพัฒนา โดยกะว่าจะให้คนอื่นๆที่ร่วมทำสามารถ
เลือกได้ว่าจะใช้ netbeans หรือ eclipse ก็ได้
ทำให้ต้องเลือกวิธีการ build + test + deploy โดยใช้ ant
หรือ maven แทนที่จะอิงกับ feature ของ Ide
จากการนั่ง setup project โดยการใช้ maven
ก็จัดแจงไป download maven version ใหม่มา
จากนั้นก็นั่ง setup project structure, files
จากการทดสอบ build พบปัญหาว่าเจ้า xdoclet plugin ที่มากับ
maven 1.0.2 มันเปลี่ยน concept ใหม่ไปเยอะเลย
ทำให้ script เก่าๆที่เคยใช้ มัน run ไม่ผ่าน
ก็เลยต้องตัดใจ เพราะถ้าเอาไปพวกน้องๆที่มาช่วย code
ซึ่งไม่คุ้นเคยกับ maven เลย ก็คงจะ
สร้างความสับสน ตลอดจนเสียเวลาเรียนรู้และ setup เยอะ
ก็เลยไป load เจ้า AppFuse มาลองดู
(เป็นนิสัยเสียอย่างหนึ่งที่ได้ version ใหม่มาแล้ว
ก็ไม่อยากย้อนกลับไปใช้ version เก่า)
เจ้า concept ของ AppFuse ก็คือ
เป็นตัวช่วย kickstart ของโปรเจค
กล่าวคือ ตัวมันจะทำหน้าที่ generate
dummy project ให้เรา โดยมี function
ในระดับที่สามารถทดสอบ run ทดสอบเบื้องต้นได้ทันที
โดยตัว function ที่มีให้ก็คือ หน้าจอ login
, หน้าจอ main, edit profile ที่ถือว่าเป็น
หน้าจอบังคับของ web application
นอกจากนี้ยัง generate file, directory structure
ต่างๆที่พร้อมจะให้เรา coding, building, testing, deploy
ผ่านทาง ant ได้ทันที
การใช้งาน (คร่าวๆ)
จากการทดลองใช้ สิ่งที่ชอบก็คือ
project structure ที่ AppFuse สร้างให้เรา
ดูมีระเบียบดีมาก
แล้วก็ setup project ได้เร็ว ไม่ต้องนั่ง
หลังขดหลังแข็ง copy file จากโปรเจคเก่า
ส่วนข้อเสีย ก็คือขนาดของ project
มันใหญ่จัง ขนาดตั้ง 50 MB ไม่รู้ว่า
มันเอา library อะไรยัดมาให้เราบ้าง
คงต้องนั่งตัดเอา library ที่ไม่ใช้ออกเสียบ้าง
ได้ยินเจ้า AppFuse มาหลายเดือนแล้ว ยังไม่ได้ลองสักที
พอดีมีโปรเจคใหม่อันหนึ่งเป็นโปรเจคเล็กๆ แค่แสดงความ
เป็นไปได้เฉยๆ ก็เลยวางแผนว่าจะใช้
Hibernate + Spring + Tapestry
แต่ปัญหาของโปรเจคใหม่ ก็คือ การ setup environment
ในการพัฒนา โดยกะว่าจะให้คนอื่นๆที่ร่วมทำสามารถ
เลือกได้ว่าจะใช้ netbeans หรือ eclipse ก็ได้
ทำให้ต้องเลือกวิธีการ build + test + deploy โดยใช้ ant
หรือ maven แทนที่จะอิงกับ feature ของ Ide
จากการนั่ง setup project โดยการใช้ maven
ก็จัดแจงไป download maven version ใหม่มา
จากนั้นก็นั่ง setup project structure, files
จากการทดสอบ build พบปัญหาว่าเจ้า xdoclet plugin ที่มากับ
maven 1.0.2 มันเปลี่ยน concept ใหม่ไปเยอะเลย
ทำให้ script เก่าๆที่เคยใช้ มัน run ไม่ผ่าน
ก็เลยต้องตัดใจ เพราะถ้าเอาไปพวกน้องๆที่มาช่วย code
ซึ่งไม่คุ้นเคยกับ maven เลย ก็คงจะ
สร้างความสับสน ตลอดจนเสียเวลาเรียนรู้และ setup เยอะ
ก็เลยไป load เจ้า AppFuse มาลองดู
(เป็นนิสัยเสียอย่างหนึ่งที่ได้ version ใหม่มาแล้ว
ก็ไม่อยากย้อนกลับไปใช้ version เก่า)
เจ้า concept ของ AppFuse ก็คือ
เป็นตัวช่วย kickstart ของโปรเจค
กล่าวคือ ตัวมันจะทำหน้าที่ generate
dummy project ให้เรา โดยมี function
ในระดับที่สามารถทดสอบ run ทดสอบเบื้องต้นได้ทันที
โดยตัว function ที่มีให้ก็คือ หน้าจอ login
, หน้าจอ main, edit profile ที่ถือว่าเป็น
หน้าจอบังคับของ web application
นอกจากนี้ยัง generate file, directory structure
ต่างๆที่พร้อมจะให้เรา coding, building, testing, deploy
ผ่านทาง ant ได้ทันที
การใช้งาน (คร่าวๆ)
- Install AppFuse โดยเลือก Framework ที่ต้องการ
- Install Ant (version 1.6.2+)
- Install Jarkatar Tomcat
- Install MySql (เป็น default database แต่ของผม ผมเลือกใช้ Postgres แทน)
- config Environment Variable
- สร้าง app ใหม่ด้วยคำสั่ง ant new
ตัว AppFuse จะ generate project structure ให้เรา - cd ไปยัง project directory แล้วก็สั่ง ant setup
แค่นี้เราก้ได้ dummy project ที่สามารถ run ได้แล้ว
จากการทดลองใช้ สิ่งที่ชอบก็คือ
project structure ที่ AppFuse สร้างให้เรา
ดูมีระเบียบดีมาก
แล้วก็ setup project ได้เร็ว ไม่ต้องนั่ง
หลังขดหลังแข็ง copy file จากโปรเจคเก่า
ส่วนข้อเสีย ก็คือขนาดของ project
มันใหญ่จัง ขนาดตั้ง 50 MB ไม่รู้ว่า
มันเอา library อะไรยัดมาให้เราบ้าง
คงต้องนั่งตัดเอา library ที่ไม่ใช้ออกเสียบ้าง
Related link from Roti
Thursday, May 12, 2005
JFugue Java API for Music Programming
project นี้มีมานานแล้ว พึ่งเห็นนี่แหล่ะ
เป็น api สำหรับการเล่น note ดนตรี
ตัวอย่าง

กรณีที่ต้องการเล่นหลายโน็ตพร้อมกัน
กรณีที่เป็น chord
เปลี่ยนเครื่องดนตรี ใช้ I (instrument) นำหน้าตามด้วย
ชื่อเครื่องดนตรี
ที่มาของชื่อ JFugue
ใครที่ต้องการรู้ definition ของ JFugue ชัดๆ
ให้ตามไปที่นี่ JFugue Anatomy
เห็นแล้วปวดหัวเลย
ส่วนอันนี้สิเด็ด เอา DNA sequence มาแปลงเป็นเพลง
Red Blood DNA Sequences
เป็น api สำหรับการเล่น note ดนตรี
ตัวอย่าง
import org.jfugue.Player;
import org.jfugue.Pattern;
/**
*
* @author pphetra
*/
public class Main {
/** Creates a new instance of Main */
public Main() {
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
Player player = new Player();
// "Frere Jacques"
Pattern pattern1 = new Pattern("C5q D5q E5q C5q");
// "Dormez-vous?"
Pattern pattern2 = new Pattern("E5q F5q G5h");
// "Sonnez les matines"
Pattern pattern3 = new Pattern("G5i A5i G5i F5i E5q C5q");
// "Ding ding dong"
Pattern pattern4 = new Pattern("C5q G4q C5h");
// Put it all together
Pattern song = new Pattern();
song.add(pattern1);
song.add(pattern1);
song.add(pattern2);
song.add(pattern2);
song.add(pattern3);
song.add(pattern3);
song.add(pattern4);
song.add(pattern4);
// Play the song!
player.play(song);
}
}
กรณีที่ต้องการเล่นหลายโน็ตพร้อมกัน
new Pattern("C5+E5+G5")กรณีที่เป็น chord
new Pattern("Cmaj5q")เปลี่ยนเครื่องดนตรี ใช้ I (instrument) นำหน้าตามด้วย
ชื่อเครื่องดนตรี
I[Piano]
ที่มาของชื่อ JFugue
Fugue is a musical style in which a common theme, often called a statement, is played, then other voices join in and play subtle variations on the statement.
ใครที่ต้องการรู้ definition ของ JFugue ชัดๆ
ให้ตามไปที่นี่ JFugue Anatomy
เห็นแล้วปวดหัวเลย
ส่วนอันนี้สิเด็ด เอา DNA sequence มาแปลงเป็นเพลง
Red Blood DNA Sequences
Related link from Roti
Wednesday, May 11, 2005
Tuesday, May 10, 2005
Linux Mainframe 's benchmark
ต่อเนื่องมาจาก
ด้วยความอยากรู้ว่าประสิทธิภาพของ linux บน mainframe
จะมีขนาดไหน ก็เลยทดลองหา benchmark ต่างๆมา
ทดลอง run ดู โดยมีเครื่องเปรียบเทียบคือ
ตัวแรกก็คือ scimark2 ตัวนี้วัดกันที่ cpu ตัวเดียว
benchmark ตัวนี้มีจุดบกพร่องหน่อยตรงไม่ได้
ควบคุม jdk ให้เป็น version เดียวกัน
IBM Unix Server
Linux Mainframe
Linux on laptop
ทดสอบ IO ด้วย Bonnie
benchmark อันนี้ java ไม่เกี่ยว
IBM Unix Server
Linux Mainframe
Linux on Labtop
สุดท้ายทดสอบด้วย volano โดยทดสอบแค่ loopback อย่างเดียว
IBM Unix Server
Linux Mainframe
Linux on Labtop
ผลลัพท์ของ benchmark ก็เป็นตัวชี้นำให้เห็น
performance ได้ระดับหนึ่ง
เนื่องจากตัว Application ที่ย้ายขึ้นไป มีลักษณะเป็น
business application ไม่ได้ตั้งหน้าตั้งตา
คำนวณแต่อย่างไร
นอกจากนั้น IO ก็ไม่ได้ระดมยิงเข้ามา
มากมายนัก (ส่วนใหญ่เป็น read เสียมากกว่า)
ไว้คอยดูตอน run จริงทั่วประเทศ
ว่าจะเป็นอย่างไร เมื่อเทียบกับของเดิม
ที่อยู่บน IฺBM Unix Server
ด้วยความอยากรู้ว่าประสิทธิภาพของ linux บน mainframe
จะมีขนาดไหน ก็เลยทดลองหา benchmark ต่างๆมา
ทดลอง run ดู โดยมีเครื่องเปรียบเทียบคือ
- Ibm Unix Server cpu powerpc 4 cpu ram 4GB, AIX, ibmjava1.3
- Linux Mainframe 2 cpu, ram 3GB, SUSE Linux, ibmjava1.3
- Labtop p4 2.4GHz 1 cpu ram 1 GB, ubuntu Linux, sunjava1.5
ตัวแรกก็คือ scimark2 ตัวนี้วัดกันที่ cpu ตัวเดียว
benchmark ตัวนี้มีจุดบกพร่องหน่อยตรงไม่ได้
ควบคุม jdk ให้เป็น version เดียวกัน
IBM Unix Server
Composite Score: 266.65277256015247
FFT (1024): 271.51941127067306
SOR (100x100): 216.80410690128483
Monte Carlo : 6.929870290609104
Sparse matmult (N=1000, nz=5000): 316.2934686121103
LU (100x100): 521.7170057260851
java.vendor: IBM Corporation
java.version: 1.3.1
os.arch: ppc
os.name: AIX
Linux Mainframe
Composite Score: 47.96155598565569
FFT (1024): 36.411169514987805
SOR (100x100): 90.41643126719151
Monte Carlo : 14.357907621051798
Sparse matmult (N=1000, nz=5000): 45.22218974459271
LU (100x100): 53.40008178045459
java.vendor: IBM Corporation
java.version: 1.3.1
os.arch: s390x
os.name: Linux
os.version: 2.4.19-3suse-SMP
Linux on laptop
Composite Score: 207.36273014140824
FFT (1024): 92.19030829632334
SOR (100x100): 362.3458191150928
Monte Carlo : 45.1455518873811
Sparse matmult (N=1000, nz=5000): 138.26159614289057
LU (100x100): 398.8703752653535
java.vendor: Sun Microsystems Inc.
java.version: 1.5.0_01
os.arch: i386
os.name: Linux
os.version: 2.6.10-5-386
ทดสอบ IO ด้วย Bonnie
benchmark อันนี้ java ไม่เกี่ยว
IBM Unix Server
-------Sequential Output-------- ---Sequential Input-- --Random--
-Per Char- --Block--- -Rewrite-- -Per Char- --Block--- --Seeks---
Machine MB K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU /sec %CPU
100 59947 96.6 248629 99.5 106319 33.2 77576 100.0 862010 101.0 92464.2 231.2
Linux Mainframe
-------Sequential Output-------- ---Sequential Input-- --Random--
-Per Char- --Block--- -Rewrite-- -Per Char- --Block--- --Seeks---
Machine MB K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU /sec %CPU
100 8307 100.0 86405 99.6 589488 103.6 8672 100.0 934170 100.4 53273.0 186.5
Linux on Labtop
-------Sequential Output-------- ---Sequential Input-- --Random--
-Per Char- --Block--- -Rewrite-- -Per Char- --Block--- --Seeks---
Machine MB K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU /sec %CPU
100 32729 91.3 146186 76.7 294090 91.9 41498 99.2 815416 93.9 844.4 2.0
สุดท้ายทดสอบด้วย volano โดยทดสอบแค่ loopback อย่างเดียว
IBM Unix Server
VolanoMark version = 2.5.0.9
Messages sent = 20000
Messages received = 380000
Total messages = 400000
Elapsed time = 16.817 seconds
Average throughput = 23785 messages per second
Linux Mainframe
VolanoMark version = 2.5.0.9
Messageon = 2.5.0.9
Messages sent = 20000
Messages received = 380000
Total messages = 400000
Elapsed time = 35.91 seconds
Average throughput = 11139 messages per second
Linux on Labtop
VolanoMark version = 2.5.0.9
Messages sent = 20000
Messages received = 380000
Total messages = 400000
Elapsed time = 34.923 seconds
Average throughput = 11454 messages per second
ผลลัพท์ของ benchmark ก็เป็นตัวชี้นำให้เห็น
performance ได้ระดับหนึ่ง
เนื่องจากตัว Application ที่ย้ายขึ้นไป มีลักษณะเป็น
business application ไม่ได้ตั้งหน้าตั้งตา
คำนวณแต่อย่างไร
นอกจากนั้น IO ก็ไม่ได้ระดมยิงเข้ามา
มากมายนัก (ส่วนใหญ่เป็น read เสียมากกว่า)
ไว้คอยดูตอน run จริงทั่วประเทศ
ว่าจะเป็นอย่างไร เมื่อเทียบกับของเดิม
ที่อยู่บน IฺBM Unix Server
Related link from Roti
CLI0125E Exception ใน WebSphere
สืบเนื่องมาจากการย้าย Application server
จาก Aix ขึ้นไปบน Linux Mainframe
ผลการทดสอบ ปรากฎว่าพบปัญหา
ก็เลยต้องไปใล่ค้นใน net ดู
ปรากฎว่าเป็นปัญหาประเภทว่า
เรา select ค่ามาใส่ result set (cursor เปิดอยู่)
จากนั้นเราก็ iterate ไปตาม result
โดยใช้ rs.next()
โดยแต่ละค่าที่ iterate จะมีการเปิด statement
ใหม่เพื่อที่จะ select ค่า (จากอีก table หนึ่ง)
ตรงนี้แหล่ะที่เป็นปัญหา
เข้าใจว่า พอเราทำอีก statement หนึ่ง
ตัว cursor ก็จะปิดไป ทำให้พอ iterate
ไปยัง result ตัวถัดไป ก็จะเกิด Exception นี้ขึ้น
ทางแก้ไข
ต้องเข้าไป set datasource property
ประเด็นที่น่าสนใจ
ทำไมใน Aix ไม่ต้อง set ก็ run ได้
อ้างอิง
To resolve a CLI0125E error, can I set the CURSORHOLD property to 1 (TRUE) for a DB2 V8.1 datasource (using WebSphere Application Server V5.0.2), as is recommended for a DB2 V7.1 datasource in earlier versions of WebSphere?
A: Yes, this solution is still valid for WebSphere Application Server V5.0.2 and a DB2 V8.1 datasource.
The performance ramifications of this setting are minimal. Currently, if the transaction is completed or closed, the cursors are not destroyed in the memory. However, they are destroyed when a specific CLOSE CURSOR is issued. Therefore, if your code follows good programming practices (that is, OPEN a cursor, FETCH rows, CLOSE cursor), and if the code does the CLOSE CURSOR for every OPEN CURSOR, then setting the CURSORHOLD property should not be a performance concern.
จาก Aix ขึ้นไปบน Linux Mainframe
ผลการทดสอบ ปรากฎว่าพบปัญหา
[9/5/2005, 16:25:13:275 ICT] 3d24ec3d SystemOut O 16:25:13,273 ERROR [UnEmpl
oyInfoFindCmd] COM.ibm.db2.jdbc.DB2Exception: [IBM][CLI Driver] CLI0125E Functi
on sequence error. SQLSTATE=HY010
COM.ibm.db2.jdbc.DB2Exception: [IBM][CLI Driver] CLI0125E Function sequence err
or. SQLSTATE=HY010
at COM.ibm.db2.jdbc.app.SQLExceptionGenerator.throw_SQLException(SQLExce
ptionGenerator.java:273
ก็เลยต้องไปใล่ค้นใน net ดู
ปรากฎว่าเป็นปัญหาประเภทว่า
เรา select ค่ามาใส่ result set (cursor เปิดอยู่)
จากนั้นเราก็ iterate ไปตาม result
โดยใช้ rs.next()
โดยแต่ละค่าที่ iterate จะมีการเปิด statement
ใหม่เพื่อที่จะ select ค่า (จากอีก table หนึ่ง)
ตรงนี้แหล่ะที่เป็นปัญหา
เข้าใจว่า พอเราทำอีก statement หนึ่ง
ตัว cursor ก็จะปิดไป ทำให้พอ iterate
ไปยัง result ตัวถัดไป ก็จะเกิด Exception นี้ขึ้น
ทางแก้ไข
ต้องเข้าไป set datasource property
Name: connectionAttribute
Type: java.lang.String
Value: cursorhold=1
ประเด็นที่น่าสนใจ
ทำไมใน Aix ไม่ต้อง set ก็ run ได้
อ้างอิง
To resolve a CLI0125E error, can I set the CURSORHOLD property to 1 (TRUE) for a DB2 V8.1 datasource (using WebSphere Application Server V5.0.2), as is recommended for a DB2 V7.1 datasource in earlier versions of WebSphere?
A: Yes, this solution is still valid for WebSphere Application Server V5.0.2 and a DB2 V8.1 datasource.
The performance ramifications of this setting are minimal. Currently, if the transaction is completed or closed, the cursors are not destroyed in the memory. However, they are destroyed when a specific CLOSE CURSOR is issued. Therefore, if your code follows good programming practices (that is, OPEN a cursor, FETCH rows, CLOSE cursor), and if the code does the CLOSE CURSOR for every OPEN CURSOR, then setting the CURSORHOLD property should not be a performance concern.
Related link from Roti
Sunday, May 08, 2005
เปิด blog เพิ่ม
วันนี้ผมเปิด blog ใหม่
สำหรับเนื้อหาในส่วนของ ERP โดยเฉพาะครับ
ตัว blog อยู่ที่นี่ครับ Link
ตัวเนื้อหา ก็จะเป็น short note ในการที่จะ
พยายามทำเข้าใจระบบ ERP ต่างๆที่มีอยู่
(เท่าที่จะหาคู่มือมาอ่านได้)
โดยจะเริ่มต้นที่ SAP ก่อน
(ยิ่งเห็นว่าค่า Train มันแพงมาก
ก็ยิ่งอยากรู้ว่ามันทำอะไรได้แค่ไหน)
ซึ่งผลลัพท์สุดท้ายที่ต้องการ
ก็คือการพัฒนาระบบ ERP ดีๆสักระบบ
ที่พัฒนาโดยคนไทยเอง
(คงไม่ใช่การลอกหรอกนะ แต่เป็นการ
ศึกษาจุดดี หรือข้อจำกัดของคนอื่นเสียมากกว่า)
สำหรับเนื้อหาในส่วนของ ERP โดยเฉพาะครับ
ตัว blog อยู่ที่นี่ครับ Link
ตัวเนื้อหา ก็จะเป็น short note ในการที่จะ
พยายามทำเข้าใจระบบ ERP ต่างๆที่มีอยู่
(เท่าที่จะหาคู่มือมาอ่านได้)
โดยจะเริ่มต้นที่ SAP ก่อน
(ยิ่งเห็นว่าค่า Train มันแพงมาก
ก็ยิ่งอยากรู้ว่ามันทำอะไรได้แค่ไหน)
ซึ่งผลลัพท์สุดท้ายที่ต้องการ
ก็คือการพัฒนาระบบ ERP ดีๆสักระบบ
ที่พัฒนาโดยคนไทยเอง
(คงไม่ใช่การลอกหรอกนะ แต่เป็นการ
ศึกษาจุดดี หรือข้อจำกัดของคนอื่นเสียมากกว่า)
Related link from Roti
Saturday, May 07, 2005
Thursday, May 05, 2005
swt + opengl plugin
OpenGL Plug-in

Article ของ Eclipse OpenGl Plugin
Using OpenGL with SWT
สำหรับผู้ที่ใช้ swing ก็มีตัวที่คล้ายๆกันคือ Jogl
สุดท้ายก็ ariticle ที่พูดถึง java3D กับ java-opengl
Article ของ Eclipse OpenGl Plugin
Using OpenGL with SWT
สำหรับผู้ที่ใช้ swing ก็มีตัวที่คล้ายๆกันคือ Jogl
สุดท้ายก็ ariticle ที่พูดถึง java3D กับ java-opengl
Related link from Roti
j4K
J4KJava Game Contest
game must be 4096 bytes or under.
ตัวอย่างเกมส์
http://www.cokeandcode.com/4k/tilt
game must be 4096 bytes or under.
ตัวอย่างเกมส์
http://www.cokeandcode.com/4k/tilt
Related link from Roti
MIR
Music Information Retrieval
M2K (java opensource)
Tools
เห็นแล้วน่าสนใจดี (แต่ชาตินี้คงไม่ได้ใช้)
คุณ bact น่าจะชอบ
M2K (java opensource)
Tools
เห็นแล้วน่าสนใจดี (แต่ชาตินี้คงไม่ได้ใช้)
คุณ bact น่าจะชอบ
Related link from Roti
Memory Leak TestCase
ตัวเนื้อหาได้มาจาก blog ของ Tim Boudreau
ปัญหาที่พบบ่อยในการเขียน Swing ก็คือ Memory leak
ซึ่งเกิดการการ add listener ระหว่าง component ต่างๆ
แล้วไม่มีการ remove ออก
ใน project netbean ซึ่งเป็น swing ขนาดใหญ่
ก็มีการจัดการกับปัญหานี้ผ่านทาง testcase + INSANE
โดย testcase ของเขามี pattern ดังนี้
object o เป็น object ที่เราสนใจว่ามีการ leak
เกิดขึ้นหรือไม่ โดยสังเกตุว่ามีการสั่ง o = null ซึ่ง
เป็นการ clear referenece ที่อ้างถึงภายใน method นี้แล้ว
ดังนั้นถ้ามี gc เกิดขึ้นเมื่อไร object o ควรจะหายไปจาก Heap
ซึ่งวิธีการตรวจสอบก็คือ wrap object o ด้วย
WeakReference ก่อนแล้วค่อยส่งไปตรวจด้วย method assertGC
ใน method assertGC มี code เขียนไว้ดังนี้
หลักๆ ก็คือวน loop 50 ครั้งเพื่อ check ว่า
เจ้า object ที่อยู่ใน weak reference หายไปหรือยัง
โดยแต่ละครั้งก็พยายาม force gc ให้เกิด
โดยมีการ simulate การใช้ heap ผ่านทาง
ArrayList alloc
กรณีที่ loop ครบ 50 ครั้งแล้ว
ref.get() != null
ก็แสดงว่า memory leak แล้ว
ก็ call findRefsFromRoot ซึ่งมีการทำงานดังนี้
ซึ่งเป็นการ dump เอา Reference ทั้งหมดออกมา
ผมยังไม่ได้หา javadoc ของ ScannerUtils ดู
แต่เดาว่า skipNonStrongReferencesFilter คงจะ
เป็นการ scan หาเฉพาะพวกที่อยู่ใน WeakReference เท่านั้น
ที่นี้ลองดูตัวอย่างที่ Tim เขาเขียนไว้
ผลลัพท์ที่ได้จากการ Test ออกมาหน้าตาประมาณนี้
จะเห็นว่า leak ครั้งนี้ขึ้นใน JMenuItem
ของ JMenuItem ยัง hold reference ถึง MyFrame$MenuAction อยู่
โดยตัวที่ทำให้ leak ก็คือ menu.add(new JMenuItem(menuAction))
ซึ่งกรณีที่เรา new JMenuItem จะเกิดการ add listener
ระหว่าง MenuAction กับ MenuItem
ผลก็คือ PopupMenu ยังมี link จาก menuItem วิ่งไป menuAction
พวก Profiler ทำการใล่ trace ดู
แต่ที่ Tim เขาแสดงให้ดูก็คือ เราสามารถเขียน Testcase
เพื่อดักจับไว้ได้ก่อนเลย ทำให้เมื่อ project evolve
ไป ถ้าเกิดมีใครมาแก้อะไรแล้วทำให้ memory leak
ตัว testcase ก็จะฟ้องออกมาทันที
ปัญหาที่พบบ่อยในการเขียน Swing ก็คือ Memory leak
ซึ่งเกิดการการ add listener ระหว่าง component ต่างๆ
แล้วไม่มีการ remove ออก
ใน project netbean ซึ่งเป็น swing ขนาดใหญ่
ก็มีการจัดการกับปัญหานี้ผ่านทาง testcase + INSANE
โดย testcase ของเขามี pattern ดังนี้
SomeObject o = new SomeObject();
something.doSomethingWith (o);
WeakReference ref = new WeakReference (o);
o = null;
assertGC ("Object still referenced", ref);
object o เป็น object ที่เราสนใจว่ามีการ leak
เกิดขึ้นหรือไม่ โดยสังเกตุว่ามีการสั่ง o = null ซึ่ง
เป็นการ clear referenece ที่อ้างถึงภายใน method นี้แล้ว
ดังนั้นถ้ามี gc เกิดขึ้นเมื่อไร object o ควรจะหายไปจาก Heap
ซึ่งวิธีการตรวจสอบก็คือ wrap object o ด้วย
WeakReference ก่อนแล้วค่อยส่งไปตรวจด้วย method assertGC
ใน method assertGC มี code เขียนไว้ดังนี้
public static void assertGC(String text, java.lang.ref.Reference ref) {
ArrayList alloc = new ArrayList ();
int size = 100000;
for (int i = 0; i < 50; i++) {
if (ref.get() == null) {
return;
}
System.gc();
System.runFinalization();
try {
alloc.add (new byte[size]);
size = (int)(((double)size) * 1.3);
} catch (OutOfMemoryError error) {
size = size / 2;
}
try {
if (i % 3 == 0) Thread.sleep(321);
} catch (InterruptedException t) {
// ignore
}
}
alloc = null;
fail(text + ":\n" + findRefsFromRoot(ref.get()));
}
หลักๆ ก็คือวน loop 50 ครั้งเพื่อ check ว่า
เจ้า object ที่อยู่ใน weak reference หายไปหรือยัง
โดยแต่ละครั้งก็พยายาม force gc ให้เกิด
โดยมีการ simulate การใช้ heap ผ่านทาง
ArrayList alloc
กรณีที่ loop ครบ 50 ครั้งแล้ว
ref.get() != null
ก็แสดงว่า memory leak แล้ว
ก็ call findRefsFromRoot ซึ่งมีการทำงานดังนี้
private static String findRefsFromRoot(final Object target) {
final Map objects = new IdentityHashMap();
boolean found = false;
Visitor vis = new Visitor() {
public void visitClass(Class cls) {}
public void visitObject(ObjectMap map, Object object) {
objects.put(object, new Entry(object));
}
public void visitArrayReference(ObjectMap map, Object from, Object to, int index) {
visitRef(from, to);
}
public void visitObjectReference(ObjectMap map, Object from, Object to, java.lang.reflect.Field ref) {
visitRef(from, to);
}
private void visitRef(Object from, Object to) {
((Entry)objects.get(from)).addOut(to);
((Entry)objects.get(to)).addIn(from);
if (to == target) throw new RuntimeException("Done");
}
public void visitStaticReference(ObjectMap map, Object to, java.lang.reflect.Field ref) {
((Entry)objects.get(to)).addStatic(ref);
if (to == target) throw new RuntimeException("Done");
}
};
try {
ScannerUtils.scanExclusivelyInAWT(ScannerUtils.skipNonStrongReferencesFilter(), vis, ScannerUtils.interestingRoots());
} catch (Exception ex) {
// found object
found = true;
}
if (found) {
return findRoots(objects, target);
} else {
return "Not found!!!";
}
}
ซึ่งเป็นการ dump เอา Reference ทั้งหมดออกมา
ผมยังไม่ได้หา javadoc ของ ScannerUtils ดู
แต่เดาว่า skipNonStrongReferencesFilter คงจะ
เป็นการ scan หาเฉพาะพวกที่อยู่ใน WeakReference เท่านั้น
ที่นี้ลองดูตัวอย่างที่ Tim เขาเขียนไว้
public class MyFrame extends javax.swing.JFrame {
MenuAction menuAction = new MenuAction();
public MyFrame() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setBounds (20, 20, 300, 300);
getContentPane().addMouseListener (new MouseAdapter() {
public void mouseReleased (MouseEvent me) {
getPopupMenu().show((Component) me.getSource(), me.getX(), me.getY());
}
});
}
JPopupMenu getPopupMenu() {
JPopupMenu menu = new JPopupMenu();
menu.add (new JMenuItem (menuAction));
return menu;
}
static final class MenuAction extends AbstractAction {
public MenuAction() {
putValue (Action.NAME, "Do Something");
}
public void actionPerformed (ActionEvent ae) {
System.out.println("Action performed");
}
}
}
ผลลัพท์ที่ได้จากการ Test ออกมาหน้าตาประมาณนี้
Testcase: testLeak(javaapplication7.MyFrameTest): FAILED
Popup menu should have been collected:
private static java.awt.Component java.awt.KeyboardFocusManager.focusOwner->
javaapplication7.MyFrame@d1e89e->
javaapplication7.MyFrame$MenuAction@f17a73->
javax.swing.event.SwingPropertyChangeSupport@3526b0->
javax.swing.event.EventListenerList@3ddcf1->
[Ljava.lang.Object;@105c1->
javax.swing.JMenuItem$1@f74864->
javax.swing.JMenuItem@110003->
javax.swing.JPopupMenu@627086
จะเห็นว่า leak ครั้งนี้ขึ้นใน JMenuItem
ของ JMenuItem ยัง hold reference ถึง MyFrame$MenuAction อยู่
โดยตัวที่ทำให้ leak ก็คือ menu.add(new JMenuItem(menuAction))
ซึ่งกรณีที่เรา new JMenuItem จะเกิดการ add listener
ระหว่าง MenuAction กับ MenuItem
ผลก็คือ PopupMenu ยังมี link จาก menuItem วิ่งไป menuAction
สรุป
โดยปกติเวลาเราสงสัยว่า memory leak หรือไม่ก็ต้องใช้พวก Profiler ทำการใล่ trace ดู
แต่ที่ Tim เขาแสดงให้ดูก็คือ เราสามารถเขียน Testcase
เพื่อดักจับไว้ได้ก่อนเลย ทำให้เมื่อ project evolve
ไป ถ้าเกิดมีใครมาแก้อะไรแล้วทำให้ memory leak
ตัว testcase ก็จะฟ้องออกมาทันที
Related link from Roti
qemu
วันนี้อ่านเจอ Qemu
ซึ่งเป็น opensource emulator
ก็เลยทดลอง run linux บนเครื่อง mac
โดยจะทดลอง run knoppix ดู
1. เริ่มแรกก็สร้าง knoppix cdrom image file
สั่ง umount cdrom ก่อน
ใช้ dd สร้าง image file
2. ใช้ qemu run จาก image file

ความเร็วที่ได้นั้นไม่เป็นที่น่าประทับใจแต่อย่างไร
ซึ่งเป็น opensource emulator
ก็เลยทดลอง run linux บนเครื่อง mac
โดยจะทดลอง run knoppix ดู
1. เริ่มแรกก็สร้าง knoppix cdrom image file
สั่ง umount cdrom ก่อน
sudo umount /Volumns/KNOPPIX
ใช้ dd สร้าง image file
sudo dd if=/dev/disk1s0 of=/tmp/testimage.img bs=2048
2. ใช้ qemu run จาก image file
qemu -cdrom /tmp/testimage.img -pci -m 512 -k en-us -boot d
ความเร็วที่ได้นั้นไม่เป็นที่น่าประทับใจแต่อย่างไร
Related link from Roti
Subscribe to:
Posts (Atom)