Thursday, April 20, 2006

css shadow, overlay

พอเริ่มใช้ lightbox go wild แล้ว ก็เลยเริ่มมองเห็นพวกที่ใช้ shadow เยอะขึ้น
อย่างอันนี้ เป็น menu ที่มีการใช้ layer เข้ามาทำเงา
Pop-Up Menus



technique ที gone-wild ใช้ จะใช้การ set สี backgroup
และกำหนด opacity
#overlay {
...
background-color:#000;
opacity:.80;
filter: alpha(opacity=80);
-moz-opacity: 0.8;
}

ส่วน popup menu ใช้ image ที่เป็น transparent background
background-image: url(shadow.png);


อ่านเพิ่มเติม

Related link from Roti

ที่นั่ง

Tor Norbye เขาเขียนเรื่อง Learn Twice As Much With Same Effort
ขอบกฎ "Second Law Of Gravitation" ที่ว่า
Students will gravitate towards the back of the class room. The seats in front fill up last.

ดูแผนผังที่นั่งเขาอ้างถึงแล้ว
สมัยที่ผมเรียน ผมนั่งกลุ่ม 31 % มาตลอดเลย
สงสัยอันนี้คงเป็นสาเหตุที่ทำให้ไม่เก่งด้านทฤษฏี

Related link from Roti

คิดถึง

เมื่อก่อนบ้านผมมีหนูมาอาศัยอยู่ด้วยเยอะมาก
ดักจับกันไม่หวาดไม่ไหว
อยู่ๆมา หนูก็เริ่มหายหน้าหายตาไป
โดยไม่มีใครเฉลียวใจ

มีอยู่วันงูหลามเลื้อยเข้ามานอนเล่นหน้าบ้าน
(บ้านอยู่แถว major รัชโยธิน)
ด้วยความเป็นห่วงกลัวว่างูจะมาหยอกรัดลูกชายผมเล่น
งูเลยต้องย้ายออกไป
ก็เลยโทรแจ้งให้เจ้าหน้าที่มาค้นย้าย
ยามที่อยู่ตรงข้ามบ้านแนะนำให้โทรไปแจ้งที่ศูยน์นเรนทร
ฟังชื่อศูนย์แล้วนึกถึงภาพ หน่วย swat ก็เลยเปลี่ยนใจโทรไป
"ร่วมด้วยช่วยกัน" แทน
ได้ผล เขาประสานงานแจ้งให้ อปพร. (อาสาสมัครป้องกันภัยพลเรือน) มาจัดการให้



ผ่านไป 2 เดือน เริ่มรู้สึกคิดถึงเจ้างูตัวนั้นแล้ว
เพราะบรรดา หนูๆ ที่เคยเงียบสงบ เริ่มก่อตั้งสมาคมขึ้นมาอีกแล้ว
บางตัวมีวิธีเจาะเข้าบ้านด้วยการมุดเข้ามาทางท่อน้ำทิ้งในห้องน้ำ
ซึ่งต้องเอาหัวดันฝาดักกลิ่นที่ครอบท่อน้ำทิ้งขึ้นมา

เฮ้อ นี่ถ้าไม่ติดลูกชาย
ก็คงจะปล่อย พี่งู ไว้เฝ้าบ้านเหมือนเดิมแล้ว

Related link from Roti

Saturday, April 15, 2006

[การเมือง] มรดกของทักษิน

ไมเคิล ไรท เขียน "ทำอย่างไรในยุคหลังทักษิณ?" ใน มติชน
แต่สามารถอ่านได้จาก มหาวิทยาลัยเที่ยงคืน ด้วย

รัฐบาลไทยรักไทยให้ความสำคัญ เข้าถึง และปลุกระดมชนชั้นราษฎรชาวชนบทอย่างไม่เคยมีมาก่อน นโยบายนี้จะมีผลดี-ผลร้ายต่อไปเรายังมองไม่เห็น แต่เราต้องยกย่องว่าเป็นกุศโลบายที่ยกฐานะการเมืองไทยจาก "การเล่นละครของชนกลุ่มน้อย" มาอยู่ในระดับ "ชีวิตจริงของคนส่วนใหญ่" ซึ่ง ดีไม่ดี หมายถึงการเติบโตทางการปกครองบ้านเมือง (Political Maturity)

นโยบายเข้าถึงและปลุกระดมราษฎรของพรรคไทยรักไทยจะดีหรือชั่ว เราตัดสินไม่ได้เพราะเหตุการณ์ยังอยู่ประชิดตัวมากไป อย่างไรก็ตาม เราแน่ใจได้ว่าเข็มนาฬิกาไม่เดินทวนกลับหรอก ราษฎรผู้ยากจนในชนบทจะไม่หลับใหลกลับเข้าสู่ยุคนิทราอีกต่อไป ต่อไปนี้พรรคการเมืองใดๆ ที่อยากบริหารบ้านเมืองก็จำเป็นต้องเข้าถึงราษฎรและแก้ปัญหาของราษฎรอย่างจริงจังไม่ใช่หลอกกันเล่น

Related link from Roti

โปรแกรมประมวลผลสอบ

การตรวจผล admission ปีนี้เละเป็นโจ็กไปเลย
ในแง่ของคนทำโปรแกรมอย่างพวกเรา ก็น่าจะมีการเผยแพร่
บทสรุปและทบทวนข้อผิดผลาดกันบ้างนะ
เป้าหมายไม่ใช่เพื่อพิพากษาคนทำโปรแกรมคนใดคนหนึ่ง
แต่เพื่อที่จะได้เรียนรู้ข้อผิดผลาดที่เกิดขึ้น และหลีกเลี่ยงไม่ให้มันเกิดซ้ำอีก

ในแง่คนนอกที่ไม่เกี่ยวข้องแบบเรา ก็ได้แต่เดาว่า เกิดอะไรขึ้นบ้าง
ที่ผมเดาๆไว้ ก็น่าจะมี 2 ประเด็นนะ
ประเด็นแรก คือเรื่องความฉุกละหุก
สังเกตงานราชการ มักจะมีปัญหานี้ทุกครั้ง
ประมูลช้า(ทำให้เหลือเวลาน้อย) หรือไม่ก็
คนเขียนขาดประสบการณ์ โปรแกรมก็เลยก้าวหน้าไปอย่างช้าๆ
ซึ่งทั้งคู่จะไปตายทีเดียวกัน ก็คือ ไม่มีเวลา test
ซึ่งเรารู้กันอยู่แล้วว่าโปรแกรมทุกโปรแกรม
มันมี bug แหง๋ๆ

อันนี้สองนี่ไม่แน่ใจว่าจริงไหม
ใครรู้จริงช่วยแย้งด้วยแล้วกัน
ไม่รู้ยังจำสมัยก่อนที่มีการนำ computer
เข้ามาควบคุมระบบไฟจราจร ในกรุงเทพฯได้หรือเปล่า
ข้อผิดผลาดครั้งนั้น ก็คือ
การ over estimate สิ่งที่โปรแกรมสามารถทำได้
โดนเชื่อกันมากไปว่า computer(software) มันต้องทำได้
ผลสุดท้าย ก็เละเป็นโจ๊กเหมือนกัน
สำหรับ admission คราวนี้ ผมเดาว่าคงมีกลิ่นนี้โชยๆอยู่เหมือนกัน
(ในแง่ของ OCR)

สำหรับผม บทเรียนที่สำคัญในเรื่องนี้ น่าจะเป็นประเด็นเรื่อง Fault tolerance
ที่ควรจะต้อง design และ implement ไว้ทั้งในระดับ work procedure
(เช่น การ scan ต้อง scan 2 ครั้ง แต่ละครั้งทำโดยทีมงานคนละชุด)
และในระดับ software fault tolerance

Related link from Roti

Friday, April 14, 2006

[java] Jetty Continuations

ใน Web container ปัจจุบัน
model ของ thread จะเป็นแบบ Thread per Connection
นั่นก็คือ 1 connection ที่วิ่งเข้ามา จะมี 1 thread ที่ถูก assign ให้รับผิดชอบ
(ผลพวงมาจาก socket api, ที่มีการ assign connection ให้กับ thread)

แนวคิดใหม่ๆ ก็คือการใช้ NIO เข้ามาช่วย
อย่างใน Jetty6 จะมี model ที่เรียกว่า Thread per Request
ความหมายก็คือ connection ที่เปิดทิ้งไว้, แต่ไม่มี request วิ่งเข้ามา
จะไม่มี thread รองรับมัน (thread จะถูกส่งกลับเข้า pool)
เมื่อไรก็ตามที่มี request เข้ามา จึงจะมีการ assign thread ให้

บางคนอาจจะสงสัยว่า 1 connection มีได้หลาย request ด้วยหรือ
Connection ใน HTTP1.1 จะมี default เป็น keep alive
และเมื่อบวกกับ App สมัยใหม่ที่เป็น ajax style
มีข้อมูลวิ่งไปมาถี่ๆ, connection ที่ browser เปิดไว้ก็แทบจะไม่ disconnect เลย
บางคนถึงกับล้อว่า นี่เราจะเปลี่ยน browser ให้กลายเป็น client-server แบบเดิมๆหรืออย่างไร
(client-server แบบที่เปิด connection ค้างไว้)

แล้ว Jetty Continuations คืออะไร
Continuations ใน Jetty ไม่ใช่ continuation แบบใน scheme, ruby (พวก callcc)
ความหมายจริงๆของมันก็คือ ในระหว่างการทำงานของ Servlet,
servlet สามารถที่จะสั่ง suspend thread ได้
โดยถ้ามีการสั่ง suspend เมื่อไร, jetty จะทำการ detach thread ออกจาก request,
และคืน thread กลับเข้าสู่ thread pool

แล้ว App ประเภทไหนที่เหมาะกับ Continations?
ที่ชัดเจนที่สุด ก็น่าจะเป็นพวกที่ต้องมีการ polling เป็นระยะๆ เช่น พวก chat
หรือพวกที่มี nature การทำงานเป็น asynchronous
เช่น app ที่มีการเรียกใช้ service ผ่านทาง JMS แล้วต้องรอ reply message

ลองมาทดสอบ NIO กับ continuation ดู
เริ่มด้วย servlet แบบง่ายๆ
protected void processRequest(HttpServletRequest request, 
HttpServletResponse response)
throws ServletException, IOException {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

out.println("<html>");
out.println("<head>");
out.println("<title>Servlet HelloServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet HelloServlet at " + request.getContextPath () + "</h1>");
out.println("</body>");
out.println("</html>");

out.close();
}

ใน servlet ให้มี sleep 100 ms,
ส่วน version ที่เป็น continuation ก็แค่เปลี่ยนจาก sleep เป็น
    Continuation ctn = ContinuationSupport.getContinuation(request,this);
ctn.suspend(100);


ผมใช้ jmeter ทดสอบ โดยเรียก 120 request ใน 1 วินาที
เป็นเวลา 20 วินาที (รวมทั้งหมด 2400 request)
ตั้ง duration assert ไว้ที่ 200 ms

Jetty 6 NIO
50 % 109 105 106
90 % 147 125 125
> 200 ms 69 91 82

Jetty 6 BIO
50 % 103 103 104
90 % 111 111 114
> 200 ms 0 2 83

Jetty 6 NIO + continuations
50 % 108 106 106
90 % 120 125 122
> 200 ms 0 78 89

Tomcat 5.0.30
50 % 104 103 103
90 % 111 114 113
> 200 ms 0 0 0

Note: 50, 90 % คือ distribution ของ response time
เช่น 50% ใช้เวลาน้อยกว่าหรือเท่ากับค่าที่ระบุ


ดูแล้ว NIO จะช้ากว่า BIO (block IO) อยู่นะ
ส่วน continuation ก็ไม่ต่างกับ NIO
และ jetty ใน version นี้เริ่มสู้กับ tomcat 5.0.x ได้แล้ว

ที่นี้ลองเปลี่ยนช่วงเวลาที่ sleep กับ suspend เป็น 1000 ms บ้าง
Thread.sleep(100);
==========================
ctn.suspend(1000);


ใช้ JMeter เรียกที่ 120 request/ sec, 20 วินาที เหมือนเดิม
แต่เปลี่ยน assert เป็น 1100 ms
ผลที่ได้

Jetty 6 NIO
50 % 1021 1008 1009
90 % 1397 1438 1419
> 1100 ms 956 919 883

Jetty 6 BIO
50 % 1002 1003 1002
90 % 1439 1549 1487
> 1100 ms 788 786 707

Jetty 6 NIO + Continuation
50 % 1002 1002 1002
90 % 1015 1011 1013
> 1100 ms 126 93 93

Tomcat 5.0.30
50 % 1005 1002 1002
90 % 1349 1035 1257
> 1100 ms 562 398 400

จะเห็นว่ากรณีที่ thread เริ่มสะสมกันมากๆ
(เนื่องจากเราตั้งค่า sleep ไว้ที่ 1 sec. ทำให้ request ชุดที่ 2 เริ่มเข้ามากวน request ชุดที่ 1)
jetty continuations ให้ผลลัพท์ที่ดีขึ้นอย่างเห็นได้ชัด

ข้อที่ต้องระวังในการใช้ jetty continuation ก็คือ
หลังจากที่เราสั่ง suspend ไปแล้ว, เมื่อ thread resume กลับมาอีกครั้ง
HttpServletRequest ที่ได้ในคราวนี้ จะไม่ใช่ตัวเดิมแล้ว
ค่า parameter ต่างๆ จะหายหมด
ดังนั้นเราต้อง save ค่านี้เก็บไว้ใน session หรือใน database ก่อน
นอกจากนี้ ยัง suspend ยังห้ามใช้หลังจากมีการเขียน response ไปแล้ว

ประเด็นที่ผมยังไม่รู้คำตอบ ก็คือ
ในการใช้งานจริง เรามักจะวาง web container ไว้หลัง web server
คำถาม ก็คือพวก persistent connection ที่เกิดขึ้น มันจะเกิดกับ web server
แล้ว jetty continuation ยังมีประโยชน์อยู่อีกหรือเปล่า
case หนึ่งที่เห็นว่า น่าจะมีประโยชน์อยู่บ้าง ก็คือพวก asynchronouse nature
(พวกที่ใช้ jms ต่อไปยัง service)
เพราะถ้าใช้ jetty contination เข้ามาช่วย ก็น่าจะทำให้เรา
เพิ่มจำนวน connection ระหว่าง web server กับ web container ได้
ซึ่งมีผล ทำให้ through put เพิ่มขึ้น

Related link from Roti

Wednesday, April 12, 2006

[art] links 12-04-06


Simone Zahradka
design เขาน่าสนใจดี, อย่าลืมลอง click ดูลูกเล่นเขา ตอนที่เลือก รูปใน portfolio


bertmonroy
digital photo-realist artist
Damen คือชิ้นงาน ที่ใครเห็นก็ต้องทึ่ง
  • The image size is 40 inches by 120 inches.
  • The flattened file weighs in at 1.7 Gigabytes.
  • It took eleven months (close to 2,000 hours) to create.
  • Taking a cumulative total of all the files, the overall image contains over 15,000 layers.

ปล. แฟนผมเห็นภาพนี้แล้วพูดออกมาว่า "น่ากลัว"
(ขยันจนน่าตกใจ)

Related link from Roti

[java, ajax] polling in DWR 2.0

Feature ที่น่าสนใจใน DWR 2.0 ก็คือ Reverse Ajax
ที่ทำให้ฝั่ง java server สามารถ call ไปยัง javascript ที่อยู่ใน browser ได้
(จริงๆแล้วมันคือ polling นั่นแหล่ะ แต่ api มันออกแบบมาให้ดูเหมือน
เป็นการ call function โดยตรง)

ลองแกะ demo ของเขาดูกัน
เริ่มด้วย browser ที่ต้องการใช้ feature นี้ต้องสั่งดังนี้
DWREngine.setPolling(true);

มาดู input form ในฝั่ง browser ก่อน
<p>
Your Message:
<input id="text" onkeypress="DWRUtil.onReturn(event, sendMessage)"/>
<input type="button" value="Send" onclick="sendMessage()"/>
</p>

<p>Messages:</p>
<div id="chatlog"></div>

จะเห็นว่าถ้ามีการกด enter หรือ click button เมื่อไร
ก็จะมีการเรียกใช้ function ที่ชื่อ sendMessage

สิ่งที่ sendMessage ทำ ก็คือ เรียกใช้ method addMessage
(สำหรับคนที่ไม่คุ้นกับ DWR, Chat object เป็น object ที่อยู่บน server,
DWR ช่วยทำให้มันเหมือนกับอยู่บน space เดียวกัน)
function sendMessage() {
var text = DWRUtil.getValue("text");
DWRUtil.setValue("text", "");
Chat.addMessage(text);
}


ตามไปดู method addMessage ที่อยู่บนฝั่ง server บ้าง
เริ่มด้วยการ validate message ก่อน
จากนั้นก็ add เข้า LinkList ที่ทำหน้าที่ maintain message history
public class Chat
{

public void addMessage(String text)
{

if (text != null && text.trim().length() > 0)
{
messages.addFirst(new Message(text));
while (messages.size() > 10)
{
messages.removeLast();
}
}


ขั้นถัดไปก็คือการ send message ไป update ที่ browser
หัวใจหลักของขั้นนี้ ก็คือ Class WebContext
Object WebContext จะคอย maintain ว่ามี browser ไหน กำลัง online อยู่บ้าง,
และกำลังใช้ page ไหนอยู่
(ความหมายของ online ก็คือ browser ยังมีการ polling มาที่ server เป็นระยะๆ)
        WebContext wctx = WebContextFactory.get();
String currentPage = wctx.getCurrentPage();

การ call กลับไปยังฝั่ง browser จะใช้ class OutboundVariable มาช่วยทำ serialize
        try
{
OutboundVariable ov = wctx.toJavascript(messages);
String eval = ov.getInitCode() + "receiveMessages(" + ov.getAssignCode() + ");"; //

สำหรับผู้ที่สงสัยว่า getInitCode ใน code ข้างบน มันทำอะไร
สิ่งที่ method getInitCode ของ OutboundVariable generate ออกมา
มีหน้าตาดังนี้
var s0=[];var s1={id:1144813303647,text:"xxxx"};
var s2={id:1144813193425,text:"hello"};
var s3={id:1144813140215,text:"I know"};
var s4={id:1144813133617,text:"yes"};
var s5={id:1144813128413,text:"from camino"};
var s6={id:1144813122412,text:"hello"};
var s7={id:1144813118411,text:"yyy"};
var s8={id:1144813116211,text:"xxx"};
var s9={id:1144813095408,text:"zzzz"};
var s10={id:1144813092406,text:"yyyy"};
s0=s0.concat([s1,s2,s3,s4,s5,s6,s7,s8,s9,s10]);

ส่วน script ที่ ov.getAssigneCode gen ออกมา ก็มีหน้าตาดังนี้
receiveMessages(s0);


จากนั้นก็ loop เพื่อ call ไปยัง page ทุก page ที่ online อยู่
พระเอกในตอนนี้ก็คือ ScriptSession

// Loop over all the users on the current page
Collection pages = wctx.getScriptSessionsByPage(currentPage);
for (Iterator it = pages.iterator(); it.hasNext();)
{
ScriptSession otherSession = (ScriptSession) it.next();
otherSession.addScript(eval);
}
}

message ก็จะวิ่งต่อกลับมายัง javascript
ให้สังเกตว่า code ข้างบน ทำการ call javascript function ที่ชื่อ receiveMessages
โดยมี parameter คือ javascript code ที่ generate จาก method ข้างบน
จากนั้นที่ฝั่ง javascript ก็จะทำการ update หน้าจอ
function receiveMessages(messages) {
var chatlog = "";
for (var data in messages) {
chatlog = "<div>" + messages[data].text + "</div>" + chatlog;
}
DWRUtil.setValue("chatlog", chatlog);
}


ถ้าเราลองไปดู http protocol ที่วิ่งระหว่าง browser กับ server
จะเห็นว่ามีการส่ง polling ออกมาเป็นระยะ
หน้าตาของ polling packet มีหน้าตาดังนี้
POST /dwr/dwr/plainjs/DWRSystem.poll.dwr HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1
....

callCount=1
c0-scriptName=DWRSystem
c0-methodName=poll
c0-id=2235_1144813311449
httpSessionId=3h2qek9hq3utl
scriptSessionId=0EADE3F10D560BBF374C1EDB48193BDA


การใช้ polling จะทำให้เกิดประเด็นตามมา ก็คือเรื่อง load ที่มากขึ้น (long lived HTTP Request)
มี solution หนึ่งที่ถูกเสนอขึ้นมา ก็คือ
Jetty 6 continuations
ซึ่งตอนนี้อยู่ในขั้น beta แล้ว

Related link from Roti

Tuesday, April 11, 2006

[Rails] Functional Test & cookie setting

วันนี้นั่งเขียน Functional test case ใน rails
เจอปัญหาว่าไม่สามารถ set cookie ได้
หลังจากพึ่ง google แล้วก็พบว่า
การ set cookie ใน test case ต้องเขียนเต็มๆอย่างนี้
def test_sort_by
@request.cookies['sort_by'] = CGI::Cookie.new(
'name' => 'sort_by',
'value' => 'location')
@request.cookies['lang'] = CGI::Cookie.new(
'name' => 'lang',
'value' => 'th')
get :index
assert_response :success
assert_equal 'location_th', @controller.check_sort_by
end


ห้ามเขียนสั้นๆแบบนี้
@request.cookies['lang'] = 'th'

Related link from Roti

M or F

seapegasus ตั้งข้อสังเกตว่า IDE มีธรรมชาติเป็นผู้หญิง
ลองไปดูเหตุผลที่นี่
NetBeans Evandalists #10

Related link from Roti

Monday, April 10, 2006

Gavin King เขียนถึงเรื่อง Red Hat to acquire JBoss
เล่าถึงสมัยก่อนเริ่มทำ hibernate
Three years ago I was dragging myself out of bed each morning to go to my depressing job building boring web applications for organizations run by risk-averse middle managers and clueless "architects".

That was a world where an idea - however unoriginal - was valued on the basis of how many grey hairs were in evidence on the head of the person expressing the idea.

บ้านเราเป็นแบบนี้หรือเปล่า ?

Related link from Roti

Sunday, April 09, 2006

[C#, LINQ] C# 3.0 กับ Haskell

Erik Meijer เขียน paper นี้ขึ้นมา
Confessions Of A Used Programming Language Salesman - Getting The Masses Hooked On Haskell
(แค่ชื่อ paper ก็เจ็บแล้ว)

ในช่วงแรกๆของ paper เขาพูดถึงความพยายามของเหล่า Haskell programmer
ที่พยายามจะ implement interface ที่ทำให้ Hashkell สามารถติดต่อกับ
โลกภายนอกได้ (imperative world)

ในท้ายบท(ที่ว่าด้วย background ของ Haskell),
เขาได้ปล่อยอารมณ์ขันออกมา
I often joke that the world’s population of Haskell program-
mers fits is a 747, and when that crashed nobody will notice.
Howerver, the world’s population of Mondrian programmers fits
in a Cessna and when that would crash nobody would really notice.

Note: Mondrian -> Internet Scripting Language

ที่ชอบมาก ก็คือประโยคนี้
Apparently, I lack the talents to entice the functional pro-
gramming community, so I decided to sell my soul to the most
popular programming paradigm, objects, and to the company
that has the biggest market share, Microsoft to save the com-
mon programmer.


บทหลังๆ จะพูดถึง Haskell เข้าไปมีอิทธิพลใน C# กับ ​LINQ อย่างไรบ้าง

หลังจาก scan paper นี้แล้ว ก็ได้ข้อสรุปอย่างหนึ่ง
C# 3.0 ที่มี Monads + Closures + Meta Programming + LINQ Framework
Java หมดทางสู้เลย
แฟน Java แบบผมก็ได้แต่ทำใจ
แล้วหันไปหากิ๊ก Ruby

Related link from Roti

Saturday, April 08, 2006

[Ingres] II7 port

ในคู่มือการต่อ jdbc ไปยัง database ingres
ระบุไว้ว่า port number มีค่า default เป็น 'II7'
ซึ่งทำให้ผมเกิดความสงสัยว่า มัน resolve ค่าเป็น integer อย่างไร
แรกสุดก็นึกว่าเป็น feature ของ unix
พยายามค้นใน google แต่ไม่พบ
สุดท้ายก็ไปเจอใน source code ของ ingres jdbc
private int getPort(String target) {
...
char c0 = port.charAt( 0 );
char c1 = port.charAt( 1 );
char c2 = (port.length() == 3) ? port.charAt( 2 ) : '0';

if (
Character.isLetter( c0 ) &&
Character.isLetterOrDigit( c1 ) &&
Character.isDigit( c2 )
)
{
c0 = Character.toUpperCase( c0 );
c1 = Character.toUpperCase( c1 );

return( 1 << 14 | (c0 & 0x1f) << 9 |
(c1 & 0x3f) << 3 | (c2 & 0x07) );
}

...
}

เวลาเราลง Ingres, จะต้องมีการระบุ Installation code
ซึ่งใช้แยก instance, configuration ของ Database Server ออกจากกัน
(1 เครื่อง, start ได้หลาย installation พร้อมๆกัน)
ค่า default ของ Installation code ก็คือ 'II'
นี่จึงเป็นที่มาของ port 'II7'
ถือว่าเป็น idea ที่ดีเหมือนกัน เพราะไม่ต้องจำเลข
เปลี่ยนมาจำตัวหนังสือแทน

Note: เลข 7 มาจากไหน?

Related link from Roti

[Ruby] แปลง float ให้อยู่ในรูป Single precision (IEEE 754)

วันนี้นั่งทดลอง convert Ingres JDBC Library ซึ่งเขียนด้วย java
ให้เป็น ruby code,
ไปเจอ code ของ Java อยู่อันหนึ่ง
Float.floatToIntBits(fValue);

method นี้ทำหน้าที่แปลง float value ให้อยู่ในรูป Single precision

ใน ruby ถ้าต้องการทำแบบเดียวกัน
[fValue].pack("g")


ใน ruby การแปลงเป็น binary format จะใช้
method pack ใน class Array
ส่วนการแปลงกลับจาก binary form ก็จะใช้
method unpack ใน class String

กรณีที่เราต้องเขียนโปรแกรมระดับ Network protocol,
bit-struct เป็น library ตัวหนึ่งที่ช่วยให้เรา pack กับ unpack ได้ง่ายขึ้น
ตัวอย่าง code ของ bit-struct
class IP < BitStruct
unsigned :ip_v, 4, "Version"
unsigned :ip_hl, 4, "Header length"
unsigned :ip_tos, 8, "TOS"
unsigned :ip_len, 16, "Length"
...
octets :ip_src, 32, "Source addr"
octets :ip_dst, 32, "Dest addr"
rest :body, "Body of message"
end

ip = IP.new
ip.ip_v = 4
ip.ip_hl = 5
ip.ip_tos = 0
...
p.ip_src = "192.168.1.4"
ip.ip_dst = "192.168.1.255"
ip.body = "This is the payload text."


Note: สำหรับคนที่ไม่คุ้นกับ ruby, unsigned ที่เห็นใน source ข้างบน
ไม่ใช่การประกาศ datatype แบบใน java หรือ c
แต่เป็นการเรียกใช้ class method เพื่อ dynamic generate instance method

Related link from Roti

Wednesday, April 05, 2006

[Rails] โครงสร้างภายในของ ActiveRecord ในส่วน attributes

สมมติเรามี table products ซึ่งมี definition ดังนี้
+-------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | varchar(120) | YES | | NULL | |
| price | decimal(10,2) | YES | | NULL | |
+-------+---------------+------+-----+---------+-------+

เวลาที่เราใช้คำสั่ง find เพื่อค้นหาข้อมูล
@product = Product.find(1)

ActiveRecord จะ delegate การทำงานต่อไปยัง database adaptor ที่เรากำหนด
ซึ่ง adpater จะ fetch ข้อมูลจาก database แล้วแปลงให้อยู่ในรูป hash table
{"name"=>"test", "price"=>"11.00", "id"=>"1"}

ให้สังเกตว่า datatype จะเป็น string หมด
Note: ต้องใช้ debugger เข้าไป step ดู จึงจะเห็น, ถ้าใช้วิีธี print debug
โดยเรียกใช้ method attributes จะเห็นเป็น datatype ที่
activerecord ทำการ convert ให้แล้ว


จากนั้น finder ก็จะทำการ new Model Object ขึ้นมา
และกำหนด instance variable @attributes
ให้มีค่าเท่ากับ hash result ที่ได้จาก database adaptor

การ access object attribute ปกติเราจะใช้คำสั่งแบบนี้

old_price = @product.price
@product.price = 12

ในครั้งแรกที่เราเรียกใช้ method price หรือ price=
method พวกนี้จะยังไม่มีอยู่จริง
แต่ Activerecord จะใช้ method method_missing ดักไว้
(method_missing เป็นกลไกของ ruby ที่ไว้ support กรณี send message ไปยัง method ที่ไม่ได้ define ไว้)
และทำการ manipulate ค่าใน @attributes โดยใช้ method
read_attribute กับ write_attribute
Note: method read_attribute, write_attribute
มีคุณสมบัติพิเศษ ก็คือ มันช่วยในการ convert datatype ให้ด้วย
ถ้าเราเรียกใช้ @product.read_attribute('price')
ผลลัพท์ที่ได้กลับมา จะอยู่ในรูปของ Float value

หลังจากนั้น activerecord ก็จะ check ว่า flag generate_read_methods
เป็น true หรือไม่ ถ้าใช่ มันก็จะทำการ generate method ให้เราอัตโนมัติ
ซึ่งมีผลให้การ access ครั้งต่อไปมี performance ที่ดีขึ้น

เวลาที่เรานำ activerecord model ไปใช้งานร่วมกับ UI layer
ค่าที่ pass มาจาก form เรามักจะกำหนดให้อยู่ในรูป hash table
ผลที่ได้ ก็คือเราสามารถนำ hashtable นั้น มา update model
ของเราได้โดยตรง (เพราะ internal structure ของ model
ก็เป็น hash เหมือนกัน)

Related link from Roti

Git (Version Control)

Git - Tree History Storage Tool

GIT is a version control system designed to handle absolutely massive projects with speed and efficiency; it is used mainly for various open source projects, most notably the linux kernel.


gitk screenshot (repository browser)

Related link from Roti

Tuesday, April 04, 2006

[clisp, perl] dynamic scope

เคยเขียนกรณี Dynamic scope & Static scope ไปที
ตอนนั้นระบุว่า scheme, javascript เป็น static scope
ส่วน ruby เป็น dynamic scope

วันนี้มาดู clisp บ้าง
clisp สามารถเลือกได้ว่าจะให้เป็น static หรือ dynamic scope
โดย default จะเป็น static scope

(setq x 5)
(defun get-x () x)
(let ((x 12)) (get-x)) ;; => 5

ผลลัพท์ที่ได้จากคำสั่งที่ 3 , get-x จะได้ค่า 5 ออกมา
(ซึ่งเราเรียกว่า static scope)
แต่ถ้าเป็น dynamic scope, get-x จะต้องได้ค่า 12 ออกมา

ใน clisp เราสามารถกำหนดให้ตัวแปรเป็น dynamic scope ได้
โดยใช้คำสั่ง defvar
(defvar y 5)
(defun get-y () y)
(let ((y 12)) (get-y))


perl ก็สามารถเป็นได้ทั้ง static หรือ dynamic scope
กรณี static
$x = 5;
sub get_x {
print "x = $x";
}
sub test_static {
my $x = 12;
get_x();
}
test_static # => x = 5

ถ้าต้องการ dynamic ก็จะเป็นแบบนี้
$y = 5;
sub get_y {
print "y = $y";
}
sub test_dynamic {
local $y = 12;
get_y();
}
test_dynamic; # => y = 12

Related link from Roti

Monday, April 03, 2006

[ruby] instance variable อีกที

object ทุก object ใน ruby สามารถมี instance variable ของตัวเองได้
2.instance_variable_set '@x', 1

2.
instance_variable_get '@x' # => 1
4.instance_variable_get '@x' # => nil
(4 - 2).instance_variable_get '@x' # => 1

บรรทัดที่น่าสนใจคือ (4 - 2).instance_vairable_get '@x'
ถ้าจิตนาการ การทำงานภายในของ interpreter ณ บรรทัดนั้น ดู
  • ขั้นแรกก็ต้อง new instance fixnum ค่าเป็น 4
  • จากนั้นก็ new instance fixnum ค่าเป็น 2
  • เรียกใช้ method - ของ instance 4 โดยมี parameter เป็น instance 2
  • เรียกใช้ method instance_variable_get บน instance fixnum 4

ปัญหาเกิดที่ขั้นที่ 3 ว่ามันเรียก instance_variable_get บน object คนละตัวกับ object 2
แล้วทำไมค่ามันยังออกมาถูก มันมีกลไกพิเศษอะไรตรงไหน

ลองเปลี่ยนจาก fixnum เป็น String ดูบ้าง
"hello".instance_variable_set '@x', 1
puts "hello".instance_variable_get '@x' # => nil
x = "hello"
x.instance_variable_set '@x', 1
puts x.instance_variable_get '@x' # => 1

จะเห็นได้ว่า instance_variable ของ 2 กับ "hello" มีพฤติกรรมไม่เหมือนกัน

ข่าวดี ไม่ใช่ทุกอย่างใน ruby ที่เป็น object อย่างเป็นทางการ
คำว่า object อย่างเป็นทางการ ก็คือ มี structure เป็นเรื่องเป็นราว เช่นมี id, methods, attributes
ถ้าจะให้ยกตัวอย่างชัดๆ ต้องดูที่ implementation ของ ruby ที่ใช้ c เขียน
struct RObject {
struct RBasic basic;
struct st_table *iv_tbl;
};

แต่ก็มีพวก special object ที่ไม่ได้ใช้ struct ในการ represent
object ต่อไปนี้ ใช้ unsigned long ตัวเดียวในการ implement object (แทนที่จะใช้ struct)
  • small integers
  • symbols
  • true
  • false
  • nil
  • Qundef (อันนี้ไม่รู้ว่ามันคืออะไร)

เหตุผลที่ต้อง implement special object แบบนี้ ก็คือเรื่อง performance
(เหมือนกับ java ที่ต้องมี primitive datatype)

ที่นี้ ruby สามารถให้ค่า instance_variable ที่ถูกต้อง ในกรณี fixnum ได้อย่างไร
ที่เป็นเช่นนี้ ก็เพราะวิธีการ implement ของ ruby เป็นแบบนี้

/* file variable.c */
static VALUE
ivar_get(obj, id, warn)
VALUE obj;
ID id;
int warn;
{
VALUE val;

switch (TYPE(obj)) {
case T_OBJECT:
case T_CLASS:
case T_MODULE:
if (ROBJECT(obj)->iv_tbl && st_lookup(ROBJECT(obj)->iv_tbl, id, &val))
return val;
break;
default:
if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj))
return generic_ivar_get(obj, id, warn);
break;
}
if (warn) {
rb_warning("instance variable %s not initialized", rb_id2name(id));
}
return Qnil;
}

จะเห็นว่ากรณีของพวก special object จะมีการใช้ generic_ivar_get
ซึ่ง function generic_ivar_get นี้จะไปค้นหาค่าจาก global hashtable ที่ share กันใช้ ระหว่าง special object
นี่เป็นสาเหตุให้ fixnum 2 สามารถเรียกใ้ช้ instace_variable_get แล้ว ยังได้ค่าเดิมกลับมา

ส่วนกรณี String, จริงๆแล้ว มันตก case เดียวกับ fixnum
แต่วิธีการ implement ของ String เป็นอีกแบบหนึ่ง
ลองดูตัวอย่างนี้
irb(main):001:0> 2.object_id
5
irb(main):002:0> (4-2).object_id
5
irb(main):003:0> "hello".object_id
175214
irb(main):004:0> "hello".object_id
169884

จะเห็นได้ว่า id ของ string มันเปลี่ยนไปทุกครั้ง
ดังนั้นการ lookup instance_variable ใน generic table ที่ต้องใช้ object_id เป็นตัวเปิด hashtable
ก็เลยไม่ได้ค่าเดิมออกมา

Related link from Roti

[ruby] instance variable ของใคร?

ลองดู code นี้แล้วทายสิว่า
ถ้าเราเรียก method x แล้วจะได้อะไรกลับมา

class Foo
@x = 1
def x
@x
end
end

f = Foo.new
f.x # => nil

ใน ruby, class ก็คือ object หนึ่ง(ไม่เหมือน java)
ดังนั้น @x ของเราก็ตือ instance variable ของ class Foo

class Foo
@x = 1
def initialize(value)
@x = value
end
def x
@x
end
def self.x
@x
end
end

f = Foo.new(8)
f.x # => 8
Foo.x # => 1

Related link from Roti

จริยธรรมของแท็กซี่

เมื่อวันเสาร์ ภรรยาผมนั่ง taxi ไปกับลูกชายตัวน้อย
taxi เอ่ยปากชวนคุย
"พรุ่งนี้ไปเลือกตั้งไหม"
แฟนผมก็ตอบสั้นๆ
"ไปค่ะ"

taxi เอ่ยต่อ "แล้วจะไปเลือกใคร"
แฟนผมเงียบไปพักใหญ่ก่อนตอบว่า
"ไม่บอกค่ะ เขาเตือนกันว่า ช่วงนี้เวลาขึ้น taxi ห้ามคุยเรื่องการเมือง
กลัวถูกไล่ลง"

taxi ก็หัวเราะ แล้วก็บอกว่า
"ผมเป็น taxi ที่มีจริยธรรมนะ
เด็ก,ผู้หญิง,คนแก่ ผมไม่ไล่ลงหรอก"

Related link from Roti

Sunday, April 02, 2006

ไชยันต์ ไชยพร


คำแถลง รศ. ดร. ไชยันต์ ไชยพร หัวหน้าภาควิชาการปกครอง
คณะรัฐศาสตร์ จุฬาลงกรณ์มหาวิทยาลัย

ผม นายไชยันต์ ไชยพร อายุ ๔๗ ปี รับราชการเป็นอาจารย์ประจำคณะรัฐศาสตร์ จุฬาฯมา ๑๔ ปี มีความยึดมั่นและพร่ำสอนให้ลูกศิษย์ได้เข้าใจและหวงแหนช่วยกันรักษาสิทธิเสรีภาพของตนในระบอบประชาธิปไตยมาโดยตลอดว่า เป็นระบอบที่ดียอมให้รัฐมีอำนาจจำกัดเท่าที่จำเป็น ส่วนการได้อำนาจและใช้อำนาจของผู้ปกครองนั้นก็จะถูกตรวจสอบควบคุมโดยสาธารณะอยู่ตลอดเวลา
มาบัดนี้ ระบอบทักษิณได้ยักยอกและยึดครองประชาธิปไตยไปโดยสิ้นเชิงแล้ว การเลือกตั้งครั้งนี้เป็นการบังคับให้ลงมติรับรองผู้นำเผด็จการเท่านั้น การยุบสภาที่เกิดขึ้นทำไปเพื่อประโยชน์ของคนคนเดียวที่ไม่ยอมรับการตรวจสอบปัญหาคอรัปชั่นและผลประโยชน์ทับซ้อนที่ได้กระทำไปด้วยความโลภโมโทสัน

ทุกวันนี้ ระบอบทักษิณยึดอำนาจบริหารไปครองอย่างเข้มแข็งยิ่งไม่ต่างจากระบบประธานาธิบดี แต่ระบบประธานาธิบดีนั้นก็ยังดีที่ฝ่ายบริหารยุบสภาไม่ได้ และมีกลไกตรวจสอบของสภา กับสื่อมวลชนอิสระที่เข้มแข็งคอยทัดทานอำนาจอยู่ การยินยอมให้ระบอบทักษิณยุบสภาหนีการตรวจสอบได้ ผนวกกับการแทรกแซงครอบงำสื่อมวลชนอย่างรุนแรงเช่นปัจจุบัน ย่อมเป็นการยอมให้เผด็จการจำแลงยึดครองประชาธิปไตย ยักยอกรัฐธรรมนูญอย่างชัดแจ้ง ที่ผมไม่อาจยอมรับได้

ในสถานการณ์เผด็จการจำแลงเช่นนี้ รัฐธรรมนูญฉบับปัจจุบันได้เล็งเห็น และให้สิทธิพื้นฐานแก่ชนชาวไทยไว้แล้ว ในมาตรา ๖๕ ว่า หากมีการแสวงอำนาจรัฐโดยผิดวิถีทางของรัฐธรรมนูญพาประเทศเข้าสู่เผด็จการเมื่อใด คนไทยย่อมมีสิทธิต่อต้านโดยสงบได้ ไม่เป็นความผิด
เมื่อประชาธิปไตยไม่เป็นประชาธิปไตย เมื่อรัฐไม่เป็นรัฐของส่วนรวม การเลือกตั้งไม่ใช่การเลือกตั้งที่เป็นประชาธิปไตย องค์กรอิสระที่ทำหน้าที่ดูแลการเลือกตั้งไม่สามารถสร้างความมั่นใจและไว้วางใจ ผมจึงขอใช้สิทธิต่อต้านตามมาตรา ๖๕ “บุคคลย่อมมีสิทธิต่อต้านโดยสันติวิธีซึ่งการกระทำใดๆที่เป็นไปเพื่อให้ได้มาซึ่งอำนาจในการปกครองประเทศ โดยวิธีการซึ่งมิได้เป็นไปตามวิถีทางที่บัญญัติไว้ในรัฐธรรมนูญนี้”

โดยขอปฏิเสธหน้าที่พลเมืองที่กำหนดไว้ในกฎหมายเลือกตั้ง เพื่อรักษาสิทธิเสรีภาพของผมโดยฉีกบัตรเลือกตั้งของผมเองในวันนี้ ซึ่งผมเห็นว่าเป็นหนทางเดียวที่รักษาสิทธิของผมไว้ไม่ให้ถูกบิดเบือนไปเป็นอื่นได้

โดยขอฝ่าฝืนกฎหมายเลือกตั้งมาตรา 108 “ผู้ใดจงใจกระทำด้วยประการใด ๆ ให้บัตรเลือกตั้งชำรุดหรือเสียหายหรือกระทำด้วยประการใด ๆ แก่บัตรเสียเพื่อให้เป็นบัตรที่ใช้ได้ ต้องระวางโทษจำคุกไม่เกินหนึ่งปี และปรับไม่เกินสองหมื่นบาท และให้ศาลสั่งเพิกถอนสิทธิเลือกตั้งมีกำหนดห้าปี”
คดีนี้ผมจะเรียนต่อเจ้าพนักงานว่า ผมขอสู้คดีในชั้นศาล โดยจะไม่ยอมรับการเปรียบเทียบปรับใดๆ โดยจะขอต่อสู้คดีนำสืบให้ศาลเห็นถึงการใช้อำนาจยุบสภาโดยบิดเบือนจากวิถีทางในรัฐธรรมนูญเป็นลำดับไป ทั้งหลักฐานการถือประโยชน์ทับซ้อนของนายกรัฐมนตรี หลักรัฐศาสตร์และนิติศาสตร์ว่าด้วยอำนาจยุบสภาของรัฐบาล กระบวนการเลือกตั้งที่ไม่โปร่งใสเป็นธรรม รวมทั้งขบวนการปิดกันข่าวสารทั้งมวล เพื่อประมวลให้ศาลเห็นได้ว่า การเลือกตั้งครั้งนี้เป็นวิธีการแสวงหาอำนาจ รักษาอำนาจของเผด็จการจำแลงอย่างชัดเจน การต่อสู้คดีของผมในครั้งนี้จะมี ศูนย์นิติศาสตร์ คณะนิติศาสตร์ มหาวิทยาลัยธรรมศาสตร์ รับเป็นทนายความ มีอดีตสมาชิกวุฒิสภา แก้วสรร อติโพธิ และเพื่อนคณาจารย์ร่วมคณะรับเป็นที่ปรึกษาคดี

ขอเรียนย้ำว่า ผมไม่มีความเจ็บป่วยทางจิต ไม่มีความหลงโลภ หรือโกรธเข้าครอบงำ ผมเพียงแต่ไม่ยอมให้ พ.ต.ท. ทักษิณ ชินวัตรและบริวาร พรากเสรีภาพที่ติดตัวผมและเป็นของผมมาแต่กำเนิดไป เท่านั้นเองจริงๆ

นายไชยันต์ ไชยพร
๒ เมษายน ๒๕๔๙

Related link from Roti

Saturday, April 01, 2006

SCA (แถม SD0)

มาอีกแล้ว Term ใหม่ที่ชื่อ SCA (ไม่ใหม่เท่าไร, white paper ที่ผมอ่าน,มัน เดือน november 2005)
SCA (Service Component Architecture)
โต้โผก็คือ IBM, SAP, IONA, BEA, Oracle, ... (ยกเว้น SUN)
ผลักดันเพื่อเป็นตัวเสริมทัพ SOA (Service Oriented Architecture)

ท้าวความ SOA นิดหนึ่งก่อน
SOA จะมี nature อย่างหนึ่งก็คือ
มันเป็น coarse-grained service
nature ของ coarse-grained service ก็คือ
มี method ให้ใช้ไม่มาก, parameter ที่่รับส่งมักจะเป็นแบบ document oriented style

ตัว SCA ก็คือ Framework ที่เราสามารถใช้
fine grained component
ประกอบขึ้นเป็น SOA Service (coarse-grained service)
(ผู้ไม่คุ้นกับ java คงจะเริ่มบ่นว่า ยุ่งจังโว้ย)

ผมเป็นพวกต้องเห็น code ก่อนจึงจะเข้าใจ concept
ดีที่ตอนนี้มี Incubator project ที่ชื่อ Tuscany
submit code เข้าไปใน apache แล้ว
ก็เลยมีตัวอย่างให้ดู

ลองดูตัวอย่าง HelloWorld service ก่อน

เริ่มด้วยการ define interface ก่อนว่า helloworld มี method อะไรให้เรียกใช้
public interface HelloWorldService {

public String getGreetings(String name);

}

จากนั้นก็ตามด้วย implementation
@Service(HelloWorldService.class)
public class HelloWorldServiceComponentImpl implements HelloWorldService {

/*
* @see org.apache.tuscany.samples.helloworld.HelloWorldServiceComponent#getGreetings()
*/

public String getGreetings(String name) {
return "Hello " + name;
}

}

เริ่มสังเกตุเห็น key concept ของ SCA แล้ว
  • Service Implementation มีลักษณะเป็น POJO (plain old java object)
  • ใช้ Annotation เข้ามากำหนด Nature


เมื่อมี interface กับ implementation แล้ว
ก็มาถึง module definition
module ก็คือ group ของ component ที่ deploy ร่วมกัน
(ในกรณี helloword เรามี component เดียว)
<module xmlns="http://www.osoa.org/xmlns/sca/0.9" xmlns:v="http://www.osoa.org/xmlns/sca/values/0.9"
name="sampleHelloworld">

<component name="HelloWorldServiceComponent">
<implementation.java class="org.apache.tuscany.samples.helloworld.HelloWorldServiceComponentImpl"/>
</component>

</module>

ตรงนี้ยังมองไม่เห็นอะไรมากนัก เพราะมันยังมีแค่ component เดียวอยู่
ไว้กลับมาดูอีกทีตอนมีหลาย component

มาดูตอนใช้งานบ้าง
ส่วนของ client ที่เรียกใช้ จะมีหน้าตาดังนี้
public class HelloWorldClient {

public static final void main(String[] args) throws Exception {

// Obtain Tuscany runtime
TuscanyRuntime tuscany = new TuscanyRuntime("hello", null);

// Start the runtime
tuscany.start();

// Obtain SCA module context.
ModuleContext moduleContext = CurrentModuleContext.getContext();

// Locate the HelloWorld service component and invoke it
HelloWorldService helloworldService = (HelloWorldService)
moduleContext.locateService("HelloWorldServiceComponent");

String value = helloworldService.getGreetings("World");

System.out.println(value);

// Stop the runtime
tuscany.stop();
}
}

จะเห็นว่ามี concept ของ Runtime
ตัว SCA เป็นแค่ spec ดังนั้นต้องมี implementation ถึงจะทำงานได้จริง
Tuscany ก็คือ implementation ที่ว่า
นอกจากนี้ จะเห็นได้ว่าเราต้อง lookup service จาก module

ลองดูตัวอย่าง Module definition ที่มีหลาย component บ้าง
<module xmlns="http://www.osoa.org/xmlns/sca/0.9" xmlns:v="http://www.osoa.org/xmlns/sca/values/0.9"
name="sampleHelloworld">

<component name="HelloWorldServiceComponent">
<implementation.java class="org.apache.tuscany.samples.helloworldmc.HelloWorldServiceComponentImpl"/>
<references>
<v:greetingPrefix>GreetingPrefixComponent</v:greetingPrefix>
<v:greetingSuffix>GreetingSuffixComponent</v:greetingSuffix>
</references>
<properties>
<v:greetingMiddle>SCA</v:greetingMiddle>
</properties>
</component>

<component name="GreetingPrefixComponent">
<implementation.java class="org.apache.tuscany.samples.helloworldmc.GreetingPortionProviderImpl"/>
<properties>
<v:greetingPortion>Hello</v:greetingPortion>
</properties>
</component>

<component name="GreetingSuffixComponent">
<implementation.java class="org.apache.tuscany.samples.helloworldmc.GreetingPortionProviderImpl"/>
<properties>
<v:greetingPortion>World</v:greetingPortion>
</properties>
</component>
</module>

เห็นแล้วอย่าพึ่งถอดใจ ปรับสายตาสักนิด จะเห็นว่า
มันใช้หลักการเดียวกับ Spring นั่นคือ Dependency Injection
การร้อย component หรือการ config component ใน SCA
จะใช้หลักการเดียวกับ spring

หลายคนจะเริ่มสงสัยแล้วว่า มันเป็น webservice อย่างไร
กรณีที่เราต้องการให้เป็น web service ก็ต้องมีการ กำหนด entry point
ใน Module ด้วย
<module xmlns="http://www.osoa.org/xmlns/sca/0.9" xmlns:v="http://www.osoa.org/xmlns/sca/values/0.9"
name="sampleHelloworld">

<entryPoint name="HelloWorldService">
<interface.wsdl interface="http://helloworldaxis.samples.tuscany.apache.org#HelloWorldServiceImpl"/>
<binding.ws port="http://helloworldaxis.samples.tuscany.apache.org#helloworld"/>
<reference>HelloWorldServiceComponent/HelloWorldService</reference>
</entryPoint>

<component name="HelloWorldServiceComponent">
<implementation.java class="org.apache.tuscany.samples.helloworldws.HelloWorldServiceComponentImpl"/>
</component>

<import.wsdl
location="wsdl/helloworld.wsdl"
namespace="http://helloworldaxis.samples.tuscany.apache.org"/>

</module>

จะเห็นประเด็นหนึ่งที่ว่า definition ของ entrypoint ได้มาจาก
file wsdl โดยตรง
และมี keyword ที่เรียกว่า "binding"
spec ของ SCA เปิดโอกาสให้เรา binding ในรูป protocol อื่นได้เช่น
    <entryPoint name="HelloWorldService">
<interface.java interface="org.apache.tuscany.samples.helloworldjsonrpc.HelloWorldService"/>
<jsonrpc:binding.jsonrpc/>
<reference>HelloWorldServiceComponent/HelloWorldService</reference>
</entryPoint>

อันนี้เป็นการ binding service เป็น JSON rpc service

ที่ขาดไม่ได้คือ key สำคัญของ SCA ไม่ได้อยู่ที่ model ของ Component อย่างเดียว
แต่อยู่ที่ SDO (Service Data Object) ด้วย
SDO เข้ามาเป็น key ได้อย่างไร
จำประโยคที่ว่า "Coarse Grain Service พึงมี parameter style เป็น Document Style"
SDO ก็คือ Data Model ที่ช่วยให้เราจัดการกับ Document Style นี้ได้ง่ายขึ้น
เพราะ เราสามารถ pass document ในรูป SDO ไปให้ Party อื่นๆ
เมื่อ Party นั้นๆ ทำการแก้ไขรายการใน document เสร็จ (เช่น fill-in, append, delete)
ก็จะส่ง document นั้นคืนมา
ซึ่งเราสามารถใช้ feature ของ SDO เข้าไปดูเฉพาะ
dataset ที่เกิดการเปลี่ยนแปลงได้
เช่นหาเฉพาะ list item ที่ถูก เพิ่ม หรือ ลบ ออกจาก document

ลองดูตัวอย่าง test case ของ SDO
เริ่มด้วยการสร้าง DataObject
(เห็นมันเป็น object อย่างนี้ แต่เวลาที่มันถูก serialize เพื่อรับ/ส่ง
ระหว่าง program แล้ว มันจะถูก serialize ในรูป xml file)

DataGraph dataGraph = SDOUtil.createDataGraph();
DataObject quote = dataGraph.createRootObject("http://www.example.com/simple", "Quote");

กรณีที่ต้องการบันทึกการเปลี่ยนแปลง
ก็จะใช้ method beginLogging กับ endLogging
เข้ามาช่วย
ChangeSummary changeSummary = dataGraph.getChangeSummary();
changeSummary.beginLogging();

quote.setString("companyName", "FlyByNightTechnology");
quote.setBigDecimal("price", new BigDecimal("1000.0"));
quote.setBigDecimal("open1", new BigDecimal("1000.0"));
...

DataObject child = quote.createDataObject("quotes");
child.setBigDecimal("price", new BigDecimal("2000.0"));

changeSummary.endLogging();


ถ้าอยากเห็นว่า IBM นำ SDO ไปประยุกต์ได้อย่างมีประสิทธิภาพอย่างไร
ต้องดู FacesClient Components Developer's Guide

ที่สำคัญคือ IBM เริ่มออก sdo api ที่อยู่บนภาษาอื่นออกมาแล้ว
มี SDO for php ที่สามารถใช้บน php ได้

Related link from Roti

Online Tutoring

Online tutoring pays off at home, abroad
online กันตัวต่อตัว
บ้านเราไม่รู้มีหรือยัง

Bikram Roy founded Chicago-based Studyloft.com in November. Already, its office in Bangalore provides tutoring to 5,400 US students. ''We're trying to leverage the accessibility of the Internet to bring students tutoring when they need it and where they need it," Roy said.


Both Growing Stars and Studyloft say that the majority of their tutors hold master's degrees or doctorates in their subjects;

Related link from Roti

Dabble DB

demo ของ Dabble DB
ได้รางวัล Best in Show Award
เป็น app ที่น่าสนใจทีเดียว
ทั้งในด้าน interface และ การนำไปใช้งาน

Related link from Roti

Tapestry in Ruby

สงสัยมานานว่า ทำไมไม่มีใคร implement
web framework แบบ component oriented ใน ruby บ้าง
วันนี้โชคดี หาเจอแล้ว
CGIKit
(ตั้งชื่อไม่สื่อเลย)
แต่โชคร้ายหน่อย ตรงที่คนเขียนเขาพึ่งจะเลิก Maintainence เอง
เหตุผลก็คือ เขาจะเปลี่ยนไปใช้ python

Related link from Roti

Friday, March 31, 2006

Hibernate Load, subclass mapping

กรณีที่เราออกแบบ class ของเราให้เป็น Inheritance mapping



ถ้าเราใช้คำสั่ง load กับ base class
Person person = session.load(Person.class, id);
Employee emp = (Employee) person; // ClassCastException here

เวลา run จะเกิด ClassCastException
โดย object ที่ return จาก method load จะอยู่ในรูป
proxy object ทำให้ cast ไม่ได้

ไปค้นหาใน Issue Tracking ของ Hibernate
พบ issue ที่ HHH-80
อธิบายว่า มันเป็นพฤติกรรมที่พึ่งมีใน Hibernate 3
กรณีที่ lazy="true" (default setting), คำสั่ง load จะได้ผลลัพท์เป็น proxy object

การแก้ไข เลือกวิิธีการใดการหนึ่งจากต่อไปนี้
  • เลือก config lazy="false"
  • ใช้คำสั่ง session.get แทน session.load

Related link from Roti

Thursday, March 30, 2006

Tapestry 4

หลังจากติดอกติดใจกับ Rails มาพักใหญ่
วันนี้ก็ได้ฤกษ์กลับไป Evaluate Tapestry4.0 อีกครั้ง
(ปัจจุบันที่บริษัทฯใช้ version 3 อยู่)

แรกสุด ก็ต้องดูที่ startup time ก่อน
เมื่อใช้กับ jetty, Tapestry จะใช้เวลา start ประมาณ 5 วินาที
อืมม์..ใช้ได้ จำได้ว่า ตอนที่เป็น beta แรกๆ มันนานกว่านี้เยอะ
ในเวลา 5 วินาทีนี้ เป็นของ Hivemind (ฐานรากของ Tapestry) ไปซะ 3 วินาที

เวลาชุดถัดไปที่ต้องวัด ก็คือเวลาในการ compile page,
tapestry ก็ใช้ technique เดียวกับ JSP
นั่นคือ เมื่อเรียกใช้ ก็จะมีการ compile
(สิ่งที่ tapestry ทำ ก็คือ การ generate class file ขึ้นมา
โดยใช้ javassist)
ผลก็คือ ใช้เวลา compile ไปเสีย 9 วินาที
อันนี้สินาน แต่ก็พอยอมรับได้ (เพราะมันทำเฉพาะครั้งแรกครั้งเดียว)

ความแตกต่างต่อไปที่เห็น ก็คือ
เดิม tapestry ต้องมี xml file ที่ใช้เป็น glue ระหว่าง
html file กับ class file
โดย xml file นี้จะระบุว่า html นี้จับคุ่กับ class file ไหน
มาใน Tapestry 4.0 นี้ มีการใช้ convention แบบ rails เข้ามาช่วย
กล่าวคือ สมมติว่า html ของเราชื่อ Home.html,
Tapestry จะมองหา class file ที่ชื่อ Home.class ใน default package
ถ้าเจอ ก็จะใช้ตัวนั้นเป็น BasePage
ซึ่งเราสามารถ config ระบุ package prefix ที่จะใช้ในการค้นหา class file ได้
<application>  
<meta key="org.apache.tapestry.page-class-packages" value="myproj.pages"/>
</application>

ส่วนกรณีที่ html page เราอยู่ใน subfolder
เวลาค้นหา BasePage, ชื่อของ subfoder ก็จะถูกแปลงเป็น packaege name ด้วย
ยกตัวอย่าง "admin/UserEdit,
การค้นหา class ก็จะค้นหา class ที่ชื่อ UserEdit
ใน package "package_prefix.admin"

ประเด็นที่ยังค้างคาใจ ใน tapestry4 ที่ต้องลองต่อ ก็คือ
การนำเอา annotation มาใช้เป็นกลไกในการ inject (dependency injection)
อยากรู้ว่า มัน work ไหม

Related link from Roti

Wednesday, March 29, 2006

หน้าตาประหลาด

ใน Signal vs Noise
เขาพูดถึง เรื่อง รูปถ่ายแมลง
มีอยู่รูปหนึ่งที่ผมติดใจมาก คือรูปนี้
Pseudocreobotra wahlbergii female

It also has a distinct “#9” spiral in yellow on top of its wing.

ถ้ามาเจอในบ้านเรา ต้องมีคนเอาเลข 9 ไปไปแทงหวยแน่เลย

ใครอยากทำความรู้จักให้มากขึ้นอ่านที่นี่
Link
(คนเขียน เขาเขียนได้ดีมากเลย)

Related link from Roti

Hibernate กับ ทักษิน

ช่วงนี้ บ้าน(ประเทศ)เรากำลังเรียนรู้หลายอย่าง
ในโลก opensource ของ java ก็เริ่มมีการปะทะชุดใหม่เกิดขึ้น

Gavin King (คนเขียน hibernate) ออกมาปะทะคารม
กรณีข้อกล่าวหา "Hibernate hates Spring"

ประเด็นของ Hibernate กับ Spring นี่
ผมไม่รู้เรื่องเลย แต่อ่านแล้ว มันมีประเด็นเรื่อง
  • ความขัดแย้งในเรื่อง JSR-220 spec
    ระหว่าง spring กับ hibernate
  • Gavin ไม่ชอบ HibernateTemplate(ใน spring) อย่างมาก (น่าสนใจว่าทำไม)

    We have already explained in other places that we dislike the noisy, inelegant HibernateTemplate stuff that Spring tries to promote. Everyone using use this stuff today should seriously reconsider.
    Until Spring redesigns their Hibernate integration stuff, we simply cannot recommend it to our users.

  • มีการ remove reference page ที่เกี่ยวกับ Spring ออกจาก Web site ของ hibernate


ส่วนประเด็นว่า แล้วมันไปเกี่ยวอะไรกับทักษิน
ไม่เกี่ยวหรอกครับ
แค่มีกลิ่นบางอย่างคล้ายๆกัน
(โปรดใช้วิจารณญาณ คนแต่ละคนรับรู้และตีความกลิ่นที่ได้รับไม่เหมือนกัน)
(กลิ่น เป็น คำอุปมา)
คือผมอ่านสิ่งที่ Gavin เขียน แล้วรู้สึกเหมือนฟังทักษินพูดอย่างไงไม่รู้
Scott ที่เป็นเจ้าของ blog ก็ยังตอบเลยว่า
I’m also surprised you think I attribute “evil” to Hibernate. The only real evil I associate with Hibernate is arrogance

(ลองเปลี่ยนคำว่า Hibernate เป็น ทักษิน ดูสิ)

Related link from Roti

Tuesday, March 28, 2006

Javascript Favelet

Bookmarklet ของดี
Javascript Object Tree Favelet
(พึ่งเคยได้ยินคำว่า Favelet นี่แหล่ะ
favelet == bookmarklet)

ช่วยให้เราใล่ javascript ได้ง่ายขึ้น
เพราะมันช่วยกรอง source code ที่เราไม่ต้องการดูออกไปได้

Related link from Roti

Implement Scheme with Haskell

พึ่งรู้เกร็ดว่า scheme48 (implementation หนึ่งของ scheme)
ได้เลข 48 มาจาก การที่มันถูกเขียนขึ้นภายใน 48 ชั่วโมง
ในปี 1996 โดยใช้ common lisp เขียน
(scheme syntax มัน minimalist มากเลย)

มาปีนี้ 2006
implementation ที่เป็น java ก็มีแล้ว (JScheme)
ถึงคราวของ haskell บ้าง
ใครที่ถึกพอ ขอแนะนำ
write yourself a scheme in 48 hours
(ใครที่อยากเห็น monads ตัวเป็นๆ ไม่ควรพลาด)

เมื่อคืนผมลองไปบ้างแล้ว
เจอแบบฝึกหัดชุดที่ 3.3 เข้าไป ก็ตัดสินใจสั่ง shutdown แล้วปิดไฟนอน

Related link from Roti

Monday, March 27, 2006

Copy/Paste from command line (Mac OS X)

pbcopy, pbpaste
เผื่อจะช่วยลดการใช้ mouse ได้อีกหน่อย

Related link from Roti

Saturday, March 25, 2006

Alex Axon Photography, Winter Session - Minimalism

Related link from Roti

Cutaway illustration

ใครอยากรู้ว่า Cutaway Illustration ที่เขียนด้วย
Illustrator + Photoshop นั้นทำอย่างไรบ้าง
ลองเข้าไปดูที่นี่

'Empress of the Seas' Cruise Ship Cutaway Illustration

ขนาด Width: 45" Resolution: 350 dpi Format: CMYK Layered Photoshop File
File Size: 1.3gb
Illustration Time: 960 Hours

เวลา click ดูรูปขยายนี่ รู้สึกหนาวเลย
ขยันจริงๆ

Note: ให้ดีต้อง click ดู HowTo ด้วย

Related link from Roti

สร้าง ruby class แบบ dynamic

ถ้าเราดูในเอกสารของ Ruby จะมีคำอธิบายถึง Class ว่า

When a new class is created (typically using class Name ... end), an object of type Class is created and assigned to a global constant (Name in this case).


ถ้าลองนำมาเขียนในแบบที่ไม่ได้ใช้ syntax ปกติดู
Object.const_set("Person", Class.new)
pok = Person.new

method const_set คือการ set ค่า global constant
ในทีนี้ก็คือการ set global constant ที่ชื่อ "Person"

ที่นี้ถ้าต้องการ define method ก็สามารถใช้พวก class_eval ได้เลย
Person.class_eval do
attr_accessor :name, :gender
def to_s
"#{name}, #{gender}"
end
end

pok.name = "polawat"
pok.gender = "male"
puts pok # => polawat, male

Related link from Roti

Friday, March 24, 2006

SwingX

SwingX ผลงานของ Romain Guy
จับ swing มาแต่งหน้า



ดูเปรียบเทีียบกับตอน layout ใน netbeans
(ใช้ matisse ด้วย)



บริเวณ header ที่มีแสงสะท้อน (gloss) กับลายแถบ (strips)
เขาเขียน code แบบนี้
private org.jdesktop.swingx.JXPanel header;

private void setupHeaderPainters() {
GlossPainter gloss = new GlossPainter(new Color(1.0f, 1.0f, 1.0f, 0.2f),
GlossPainter.GlossPosition.TOP);

PinstripePainter stripes = new PinstripePainter();
stripes.setPaint(new Color(1.0f, 1.0f, 1.0f, 0.17f));
stripes.setSpacing(5.0);

MattePainter matte = new MattePainter(new Color(51, 51, 51));

header.setBackgroundPainter(new CompoundPainter(matte, stripes, gloss));
}

MattePainter คือ การลงสี
สังเกตว่ามีการเรียงลำดัับใน CompoundPainter ด้วย

ส่วน wood surface เขาลงสีด้วย BasicGradientPainter
เพื่อให้มีเงาของ menu ข้างบน
จากนั้นก็ใส่ Noise ด้วย NoiseFilter
ตบให้ noise กลายเป็นเส้นด้วย MotionBlurFilter
(พวก xxxFilter นี่เขาใช้ library ของ JH Labs (Java Image Processing)
BasicGradientPainter gradient = new BasicGradientPainter(
new GradientPaint(new Point2D.Double(0.0, 0.0), new Color(0xd67801),
new Point2D.Double(0.0, 1.0), new Color(0xb35b01)));
NoiseFilter noise = new NoiseFilter();
noise.setDistribution(NoiseFilter.GAUSSIAN);
noise.setMonochrome(true);
MotionBlurFilter blur = new MotionBlurFilter();
blur.setDistance(1.0f);
BrushedMetalFilter metal = new BrushedMetalFilter();
metal.setMonochrome(false);
gradient.setEffect(new CompoundEffect(new ImageEffect(noise),
new ImageEffect(blur)));



ปัญหาของการใช้ effect นี้ ก็คือความเร็ว
เครื่องผม (g5) ใช้เวลา render ไป 3 วินาที

เห็นผู้เขียนเขาบอกว่าใน Mustang จะดีกว่านี้
ไม่มีปัญหาเรื่องกระตุก

ใครสนใจ code ลองไป checkout ออกมาเล่นดูได้

Related link from Roti

สร้าง Maven2 Archetype Plugin

วันนี้ลองสร้าง archetype ดู
เป้าหมายก็เพื่อเอาไว้ทำ template สำหรับ setup project ใหม่ๆ
เนื่องจากช่วงนี้มีน้องใหม่เข้ามาทำงานหลายคน
ต้องมี tool เข้ามาช่วยหน่อย
(ไม่งั้น เวลา set project ใหม่ๆ เพื่อไว้ train
ก็ต้องมานั่ง copy, paste กันวุ่นวาย)

เริ่มด้วย
structure ของ Archetype Plugin ต้องมีหน้าตาดังนี้



เริ่มแรกสุดต้องมี file pom.xml
ซึ่ง file ที่เก็บ information ของ plugin เรา
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>mx.plugin.arch.struts</groupId>
<artifactId>strutsApp</artifactId>
<version>0.1</version>
<packaging>maven-plugin</packaging>
</project>


Directory archetype-resources
คือ directory ที่เก็บ template file ของเรา
มีโครงสร้างและ files ตามที่เราอยากให้ archetype generate



file ที่สำคัญที่สุดก็คือ src/main/resources/META-INF/archetype.xml
เนื่องจากเป็น file ที่ใช้บอกว่า archetype ต้อง copy file อะไรบ้าง
<archetype>
<id>strutsApp</id>
<sources>
<source>src/main/java/Main.java</source>
</sources>
<testSources>
<source>src/test/java/Main.java</source>
</testSources>
<resources>
<resource>src/webapp/WEB-INF/web.xml</resource>
<resource>src/webapp/WEB-INF/struts-bean.tld</resource>
<resource>src/webapp/WEB-INF/struts-config.xml</resource>
<resource>src/webapp/WEB-INF/struts-html.tld</resource>
<resource>src/webapp/WEB-INF/struts-logic.tld</resource>
<resource>src/webapp/WEB-INF/struts-nested.tld</resource>
<resource>src/webapp/WEB-INF/tiles-defs.xml</resource>
<resource>src/webapp/WEB-INF/validation.xml</resource>
<resource>src/webapp/WEB-INF/validation-rules.xml</resource>
<resource>src/webapp/WEB-INF/classes/MessageResources.properties</resource>
</resources>
</archetype>


เวลาที่ archetype สร้าง file ให้เรานั้น
archetype ไม่ได้แค่ copy file เฉยๆ
แต่มันจะ transform file ให้เราด้วย
โดยมันจะใช้ velocity เข้ามาช่วย
(file ที่เราเตรียมไว้ จะกลายเป็น input template ของ velocity)

ยกตัวอย่าง file web.xml
ถ้าเรากำหนดเนื้อหาแบบนี้

<web-app>
<display-name>${artifactId}</display-name>

ตอนที่ velocity transform template แปลง
ก็จะมองหา System Property ที่ชื่อ artifactId
ถ้าหาได้ ก็จะนำมาแทนค่าให้

ยังมี case พิเศษอีกคือ
กรณีที่เป็น java code
(archetype รู้ได้จากการใช้ tag sources และ testSources
ใน archetype.xml)
ตอนที่ archetype แปลงนั้น
มันจะทำการแปะ package (สร้้าง directory) ให้เราด้วย
โดยชื่อของ package จะตั้งตาม System Property ที่ชื่อ groupId
ดังนั้นกรณีที่เราไม่อยากให้มันยุ่งกับ java file เรา
ก็กำหนดให้มันเป็น resource แทนที่จะเป็น source

หลังจากเตรียม file ทั้งหมดเสร็จ
ก็ให้ใช้คำสั่ง
mvn install

เพื่อสั่งให้ maven pack plugin เรา และ deploy เข้าไปไว้ใน local repository

เมื่อต้องการใช้ ก็แค่สั่ง (ไม่ใช่ "แค่" เพราะคำสั่งโคตรยาวเลย)
mvn archetype:create -DarchetypeArtifactId=strutsApp -DarchetypeGroupId=mx.plugin.arch.struts -DartifactId=pr1 -DgroupId=mx.pok.test -DarchetypeVersion=0.1   


อ่านเพิ่มเติม
http://maven.apache.org/guides/mini/guide-creating-archetypes.html

Note: เอกสารที่อ้างถึง อธิบายตกไปบางจุด
โชคดีที่มีคน submit project ตัวอย่างเข้ามาใน svn ของ apache
เมื่อ 2 วันก่อน ผมก็เลยรอดตายมาได้
ไม่งั้นคงไล่ไม่เจอ

Related link from Roti

Thursday, March 23, 2006

Torta

Torta เป็นโปรแกรมที่เขียนด้วย clisp
ช่วยในการ present harddisk ของเรา ว่ามีอะไรอยู่บ้าง
โดยมันจะ scan directory ที่เรากำหนด
จากนั้นก็สร้าง flash file ที่แสดง output ให้เรา
 (torta:torta "/Users/pphetra" :output "/tmp/pok.swf")


hdpok

ของเครื่องผม เบอร์หนึ่งคือ Movie ของเจ้าลูกชาย (ไม่ใช่หนังโป๊นะ)
ส่วน image ภาพศิลป์นั้น ไม่กินที่เยอะอย่างที่คิด

ถ้าดูพื้นที่ทำงาน, Java project ของผมกินที่ไป 400 MByte

ที่กินที่เยอะโดยไม่รู้ตัว ก็คือ cocoon
พี่แกเล่นกินที่ส่วน build ไป 336 MByte
(อันนี้คนละพื้นที่กับ java project)

rails ไม่ต้องพูดถึง code มันน้อยๆ
ไม่ติดฝุ่นเขาเลย

Related link from Roti

emacs+ruby, scheme

ผมเคยเขียน Customize Syntax Highlight
ด้วย Ruby ไปที
ตอนนั้นใช้ Ruby Syntax 1.0.0 ช่วยทำ

วันนี้มีเหตุให้ปัดฝุ่นกลับมาใช้อีกครั้ง
แต่คราวนี้มาในรูปแบบพิสดารกว่าเดิม
เป้าหมายในคราวนี้คือ

ตัวโปรแกรมจะ parse ruby code
output ที่ได้จะอยู่ในรูป scheme code
(syntax highlight ในรูปแบบ scheme code)
โดยโปรแกรมต้องสามารถเรียกจาก emacs ได้โดยตรง

ตัวโปรแกรม ruby เขียนไม่ยาก
แค่ extend Syntax::Convertors::Abstract
class Ruby2SS < Syntax::Convertors::Abstract

def convert(text)
str = "(page-para (code-block (code-line "
regions = []
@tokenizer.tokenize(text) do |tok|
str << to_scheme(tok)
end
str << ')))'
end

def to_scheme(tok)
case tok.group
when :punct :
if tok =~ /"|'/
"(code 'punct \"\\#{tok}\")\n"
else
"(code 'punct \"#{tok}\")\n"
end
when :normal :
if tok =~ /[^\n]*\n([^\n]*)/
")\n (code-line (code 'normal \"#{$1}\") \n"
else
"(code 'normal \"#{tok}\")\n"
end
else
"(code '#{tok.group} \"#{tok}\")\n"
end
end

end


code ที่ได้หน้าตาแบบนี้
(page-para (code-block 
(code-line (code 'ident "order")
(code 'normal " ")
(code 'punct "=")
(code 'normal " ")
(code 'constant "Order")
(code 'punct ".")
(code 'ident "new")
)
(code-line (code 'normal "")
(code 'ident "order")
(code 'punct ".")
(code 'ident "name")
(code 'normal " ")
(code 'punct "=")
(code 'normal " ")
(code 'punct "\"")
(code 'string "")
(code 'string "pok")
(code 'string "")
(code 'punct "\"")
)))


ซึ่งจะเอาไป output ออกเป็น image อีกที (ไม่ขอพูดถึงตอนนี้)

เวลาใช้ก็ต้อง integrate เข้ากับ emacs
โดยผมเลือกเอา el4r มาใช้
โดยต้องเขียน function ขึ้นมา 2 ตัว
ตัวแรกเป็น ruby function ที่อยู่ใน form ที่ emacs สามารถ call ได้
defun(:ruby_syntax_to_scheme,
:interactive => false) { |code|
Ruby2SS.new(Syntax::load('ruby')).convert code
}

ส่วนอีกตัว ก็คือ elisp ที่ทำหน้าที่ดึงข้อมูลจาก buffer แล้วส่งต่อ
ให้ ruby function จากนั้นก็นำผลลัพท์ไปใส่ไว้ใน clipboard
(defun cv-ruby-syntax-to-scheme-slide (beg end)
(interactive "r")
(let ((str (filter-buffer-substring beg end))
(x-select-enable-clipboard t))
(kill-new (ruby-syntax-to-scheme str) 'replace)))
(define-key ruby-mode-map "\C-c\C-p" 'cv-ruby-syntax-to-scheme-slide)


(จริงๆเขียนฝั่ง ruby ตัวเดียวก็ได้ แต่ขึ้เกียจ debug หรือ หัดใช้ el4r มากกว่านี้
ก็เลยเอาแค่ทำงานได้)

Related link from Roti

ECF

หลังจากมี EMF, GEF, GMF
(Graphical Editor Framework,
Eclipse Modelling Framework,
Graphical Modelling Framework)

ตอนนี้ก็ถึงคราว ECF บ้าง
ย่อจาก Eclipse Communication Framework
เป้าหลักๆของ ECF น่าจะทำไว้เพื่อเป็น Facility ของ
Real-time collaborative editing
แบบที่ jcreator มี

Related link from Roti

Eclipse releng

วันนี้นั่งอ่าน presentation ของ Eclipse releng (Release Engineering)
From Developer to Download: A Tour of the Eclipse Platform Build Factory
น่าสนใจดี

process ของ releng เริ่มจาก
developer จะ submit เข้ามาว่า
stable version ล่าสุดของตนคืออะไร
ผ่านทาง map file
ที่มีหน้าตาประมาณนี้

plugin@org.eclipse.ui=I20060117-0800,:pserver:anonymous@dev.eclipse.org:/cvsroot/eclipse

ข้อมูลนี้จะไว้ใช้ build integration builds
ส่วน nightly build จะ fetch map file จาก cvs HEAD ตรงๆเลย

script และ platform (software) ที่จะใช้ในการ build
จะถูกเก็บไว้ใน local CVS ที่อยู่บนเครื่องอีกเครื่องหนึ่ง
เพื่อให้สามารถเปลี่ยน build machine ได้ง่ายๆ (กรณี machine failure)

เมื่อถึงรอบเวลาที่จะทำการ build
build machine (G5 + Yellow Linux)
ก็จะทำดังนี้
  • checkout target code จาก dev.eclipse.org
  • checkout JDK
  • เรียก Ant file เพื่อทำการ build

น่าสนใจนะว่า JDK ก็อยู่ใน repository ด้วย

เสร็จจาก build ก็เป็นเรื่อง test
test แบ่งหัวเรื่องที่ test ออกเป็น
  • JUnit Tests
  • Performance tests
  • Verification for translation
  • Javadoc
  • API scanner
    scan หา internal api ?ไม่แน่ใจว่าตรงนี้หาที่ไหน
    ที่ javadoc หรือที่ไหน

ในส่วนของ JUnit กับ Performance Test
จะทำขนานกัน
(โดยใน Ant script จะใช้คำสั่ง ant parallel task )

JUnit test จะ test บน Windows, Linux, Mac
ส่วน Performance Test จะใช้ 5 เครื่อง
ประกอบด้วย 3 linux, 2 Window
เป็นเครื่องเร็วๆ 3 เครื่อง, เครื่อง cpu ช้าๆ 2 เครื่อง
ผลลัพท์ที่ได้ จะเก็บใน Derby Database Server

เครื่องที่ใช้ใน Performance Test
จะถูก Ghost ทุกๆ อาทิตย์ เพื่อที่จะได้ไม่มีตัวแปรเรื่อง
software configuration ที่จะไปรบกวน ผลการ test

เมื่อผ่านการ test ทั้งหมด
ก็เป็นขั้นการ publish ผลลัพท์
ขั้นตอนก็คือ ใช้ template สร้าง web page
ใน build machine
จากนั้นก็ใช้ rsync sync ข้อมูลกับ public site

ที่ชอบใจก็คือ เขา list ให้ดูเล่นๆว่า
failure ที่เกิดขึ้น มาจากอะไรได้บ้าง
(เขาใช้คำว่า "bloopers" -> "an embarrassing error" )
Usual Suspects
  • สะกดผิด เช่น map file ระบุ version อะไรมาก็ไม่รู้
  • disk space เต็ม
  • Screen savers interupt macro tests
  • Virus Scanner ตื่นขึ้นมาทำงาน ระหว่าง test
  • X window crashes
  • Network fail ระหว่าง checkout

Extraordinary Build Bloopers
  • ฟ้าผ่า
  • ไฟดับ
  • overheated เพราะ แอร์ไม่เย็น


สุดท้ายก็เป็นขั้นตอน release
ที่ต้องจัดการเรื่อง Mirror Servers ทั้งหลาย
รวมทั้ง backup servers
ตัวเลขที่น่าสนใจ ก็คือ
jar file ที่ถูก download จาก eclipse.org โดยตรง
จะตกประมาณวันละ 1,351,152 ครั้ง

Related link from Roti

Tuesday, March 21, 2006

Rails Localization Plugin

Localization Plugin เป็น Plugin ที่ช่วยทำ localize อย่างง่ายๆ
ไม่ต้องไปยุ่งกับโปรแกรม gettext

หลักการ ก็คือเราต้องสร้าง ruby program ใต้
${RAILS_ROOT}/lang
จะแยกเป็น file ตาม language หรือ รวมทุก lang ไว้ใน file เดียวก็ได้
เนื้อหาใน file จะเป็นดังนี้
Localization.define('th') do |l|
l.store 'yes', 'ตกลง'
l.store 'no', 'ไม่ตกลง'
l.store 'hello world', 'สวัสดีครับ'
end

เวลาใช้ใน view ก็เรียกใช้ผ่าน method _
 <body>
<%= _ 'hello world' %>
</body>

การ switch ภาษา จะทำผ่าน Object Localization

Localization.lang = params[:lang] == nil ? :default : params[:lang]


feature ที่ดูน่าสนใจ อยู่ที่ตรงการ Localization.define
ที่เราสามารถทำอะไรแบบนี้ได้
l.store '(time)', lambda { |t| t.strftime('%H:%M') }

ใน view จะเขียนแบบนี้
<%= _ '(time)', Time.now %>

Related link from Roti