Friday, July 22, 2005

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

เวลา setup project ใหม่ๆ มักจะมีแรงเสียดทานในช่วงแรกๆ
ซึ่งมักจะต้องเสียเวลาในการ setup environment ต่างๆให้เข้าที่เข้าทาง

อย่างกรณี การใช้ Hibernate
ที่ผมเลือกวิธีการใส่ XDoclet Tag ลงไปใน java code โดยตรง
แล้วค่อยใช้ XDoclet ในการ generate Mapping file ออกมา

ทุกๆครั้งที่เริ่ม project ใหม่ (กรณีถ้าใช้ ant)
ก็ต้องมีการ copy XDoclet library ไปไว้ที่ lib
จากนั้นก็ต้องเขียน(copy) Ant Target ที่ทำหน้าที่
scan java file เพื่อสร้าง hbm.xml file
(ไม่ยาก แต่น่าเบื่อ และช้า เพราะนานๆทำที
ก็จะจำไม่ค่อยได้ว่าต้องทำอะไรบ้าง)

การใช้ maven เข้ามาช่วย ก็เป็นการแก้ปัญหาได้อีกวิธีหนึ่ง
เนื่องจาก maven มี XDoclet Plugin อยู่แล้ว
แค่กำหนด properties บางตัวเพิ่ม
ก็สามารถเรียกใช้ Hibernate Doclet Task ได้เลย

แต่เนื่องจากผมเป็นคนที่จิตไม่ค่อยหยุดนิ่ง
ก็เลยมองหาวิธีใหม่ๆเรื่อยๆ
ตอนนี้สนใจอยู่วิธีหนึ่ง
ก็คือการ implement Eclipse Plugin
ที่ทำหน้าที่ในการสร้าง Hibernate hbm.xml file โดยเฉพาะ

หลังจากลอง search ดูใน net แล้ว
ก็เจอว่า JBoss IDE Plugin มี module
XDoclet อยู่ในตัวด้วย
แต่ยังไม่ค่อยถูกใจนัก เนื่องจากเราต้องการใช้แค่ส่วน
Generate Hibernate 's Mapping file
เพียงอย่างเดียว แต่ได้ของแถมมาเต็มไปหมด
(เดี๋ยวนี้รู้สึกว่า Eclipse ที่ลงไว้ มันช้าลงทุกวัน
ตอนนี้ก็เลยโทษ พวก plugin
ที่ประดังใส่เข้ามานี่แหล่ะ ที่เป็นต้นเหตุ)

เริ่มแรกสุด Extension Point ที่เราสนใจ
Implement ก็คือ Extension Point
ในส่วนของ org.eclipse.core.resources.builders
ที่ทำหน้าที่ transform resource file
(อย่าง java builder ก็ทำหน้าที่แปลง java file
ให้เป็น class file)

โดย builder จะถูกเรียกใช้ได้ 2 กรณี
ก็คือ
  • เรียกใช้เมื่อ platform พบว่า resource file มีการเปลี่ยนแปลง (incremental build)
  • เรียกใช้กรณีที่ user สั่ง clean ซึ่งเท่ากับเป็นการบังคับ
    ให้ build project ใหม่หมด (full build)


ใน 1 project สามารถมี builder ได้มากกว่า
1 ตัว โดยจะทำงานตามลำดับที่เรา config ไว้

การที่จะใส่ builder เข้าไปใน project ได้นั้น
เรามักออกแบบให้ builder depend กับ nature ของ project
โดยมักออกแบบให้ user สามารถ เพิ่มหรือลด nature ของ project
ได้จากหน้าจอ Project Properties
(อีกทีที่นิยม ก็คือที่จุดเริ่มต้นของการสร้าง new project
โดยมี template ให้เลือกเลย ว่าต้องการ project แบบไหน)

แน่นอนว่าการ implement nature เพิ่ม
ก็ต้องทำผ่าน Extension Point ของ Eclipse เช่นกัน
(นี่เป็นสาเหตุที่ผมชอบ architecture ของ eclipse
เป็น design ที่งามจริงๆ)
โด extension point นี้มีชื่อว่า org.eclipse.core.resources.natures

ส่วน extension point อันถัดไปก็คือ org.eclipse.ui.propertyPages
ที่เราใช้เป็น UI ที่รับคำสั่ง add nature หรือ remove nature
โดย extension ในส่วนนี้ จะทำให้ user เห็น ui
ของเราในหน้าจอ Project Properties ได้

ลองมาดู source code กันบ้าง
เริ่มด้วยการสร้าง Builder ขึ้นมาจาก IncrementalProjectBuilder
โดยในตอนนี้ยังไม่มีการ implement การทำงานจริง (ทำโครงก่อน)
public class HbBuilder extends IncrementalProjectBuilder {

public static final String BUILDER_ID = "com.mx.plugins.hbdoclet.hibernateDo
cletBuilder";

protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException {
// TODO Implement Logic here

return null;
}

}


add Extension Point org.eclipse.core.resources.builders
<extension
id="hibernateDocletBuilder"
name="Hibernate Doclet Builder"
point="org.eclipse.core.resources.builders">
<builder>
<run class="com.mx.plugins.hbdoclet.HbBuilder"/>
</builder>
</extension>


จากนั้นก็ทำการ add Nature ใหม่ที่เราต้องการเข้าไปใน platform
โดยใช้ extension point org.eclipse.core.resources.natures
<extension
id="hbdocletnature"
name="Hibernate Doclet Nature"
point="org.eclipse.core.resources.natures">
<runtime>
<run class="com.mx.plugins.hbdoclet.ProjectNature"/>
</runtime>
</extension>

ตัว ProjectNature ของเรา เขียนง่ายๆดังนี้
public class ProjectNature implements IProjectNature {

protected IProject project;

public void configure() throws CoreException {
IProjectDescription desc = project.getDescription();
ICommand[] commands = desc.getBuildSpec();
boolean found = false;

for (int i = 0; i < commands.length; i++) {
if (commands[i].getBuilderName()
.equals(HbBuilder.BUILDER_ID)) {
found = true;
break;
}
}

if (! found) {
ICommand command = desc.newCommand();
command.setBuilderName(HbBuilder.BUILDER_ID);
ICommand[] newCommands = new ICommand[commands.length + 1];

System.arraycopy(commands, 0, newCommands, 0, commands.length);
newCommands[commands.length] = command;
desc.setBuildSpec(newCommands);
project.setDescription(desc, null);
}
}

public void deconfigure() throws CoreException {
...
}

public IProject getProject() {
return project;
}

public void setProject(IProject project) {
this.project = project;
}

}


จากนั้นก็ทำการ add PropertyPages Extension
<extension
point="org.eclipse.ui.propertyPages">
<page
adaptable="true"
class="com.mx.plugins.hbdoclet.properties.ProjectPropertyPage"
id="com.mx.plugins.hbdoclet.properties.ProjectPropertyPage"
name="hbdoclet"
objectClass="org.eclipse.core.resources.IProject">
<filter
name="nature"
value="org.eclipse.jdt.core.javanature">
</filter>
</page>
</extension>

หลักการของ property page ก็คือ เราต้องระบุว่า
Object อะไรที่เราต้องการให้ set property ตัวนี้ได้
ในกรณีของเรา ก็คือ org.eclipse.core.resources.IProject
ซึ่งหมายถึง ตัว project
(กรณีถ้าต้องการ property ระดับ file
ก็จะใช้ org.eclipse.core.resources.IFile)
ส่วน filter นั้นใช้ในการกรอง project
คือยอมให้เฉพาะ project ที่มี attribute nature
เป็น JavaNature เท่านั้น ถึงจะยอมให้เห็น property นี้

ตัว class om.mx.plugins.hbdoclet.properties.ProjectPropertyPage
ของเรา extends มาจาก org.eclipse.ui.dialogs.PropertyPage

หน้าตาของ propertyPage ที่ design ไว้เบื้องต้น
จะมีหน้าตาประมาณนี้


เมื่อ user กดปุ่ม ok
เราก็จะทำการสั่งให้มีการ apply Nature หรือ remove Nature
โดยใช้ IWorkspaceRunnable เพื่อจะได้ spawn background thread สำหรับ run โดยเฉพาะ

protected void doOk(IProgressMonitor monitor) throws CoreException {
IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
if (hbdocletNatureCheck.getSelection()) {
// add nature
HbdocletPlugin.addNatureToProject(getProject(), true);
} else {
// remove nature
HbdocletPlugin.removeNatureFromProject(getProject());
}
if (hbdocletNatureCheck.getSelection()) {
IProject project = getProject();
project.build(IncrementalProjectBuilder.FULL_BUILD,
monitor);
}
}

};
HbdocletPlugin.getWorkspace().run(runnable, monitor);
}

protected IProject getProject() throws CoreException {
IProject project = (IProject) (this.getElement()
.getAdapter(IProject.class));
return project;
}

ให้สังเกตุว่าเราใช้ IWorkspaceRunnable เพื่อที่จะ apply nature โดยใช้ background thread

การ apply nature เข้ากับ project ทำดังนี้
public static void addNatureToProject(IProject project, 
boolean forceOrder) throws CoreException {

IProjectDescription description = project.getDescription();
List natures = new ArrayList(Arrays.asList(description.getNatureIds()));
if (!natures.contains(NATURE_ID)) {
if (forceOrder) {
natures.add(0, NATURE_ID);
} else {
natures.add(NATURE_ID);
}
description.setNatureIds((String[]) natures
.toArray(new String[natures.size()]));
project.setDescription(description, null);
}
}


ตัว key ของการ apply nature ก็คือ IProjectDescription
ที่ทำให้เรา access Project Description file ได้

สำหรับตัว Project Description File ใครที่เคยใช้
command line เข้าไปดูใน project directory คงจะเคยเห็น file ที่ชื่อ
.project มาแล้ว เจ้า file นี้แหละที่เป็น serialize ของ Project Description File

หลังจากที่ได้ add nature เข้าไปแล้ว
หน้าตาของ .project
ก็เป็นดังนี้
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>testproject</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.mx.plugins.hbdoclet.hibernateDocletBuilder</na
me>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.mx.plugins.hbdoclet.hbdocletnature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

Related link from Roti

Thursday, July 21, 2005

Condition Break point in Eclipse

พึ่งรู้ว่า eclipse มี feature นี้ด้วย (อ่านเจอจาก planet eclipse)
เป็น breakpoint แบบระบุเงื่อนไข

เช่นสมมติเรามี java code ดังนี้



เราสามารถ click ขวาที่ breakpoint ที่ตั้งไว้
จากนั้นก็เลือก menu "breakpoint properties"




เยี่ยมเลย feature นี้

ปล. ถ้าสังเกตุดูที่ listbox ด้านซ้าย จะมีหัวข้อ
filtering ด้วย
ข้างในมีหัวข้อให้เรากำหนดเพิ่มเติมได้ว่า
เราต้องการ debug เฉพาะ thread ไหน

Related link from Roti

Monday, July 18, 2005

Web interface ที่ไม่ต้องใช้ mouse click

วันก่อนเล่น game Loop ที่ไม่ต้องใช้ mouse click ไปล้ว
วันนี้ลองท่อง web โดยไม่ใช้ mouse click ดูบ้าง
http://www.dontclick.it/

Related link from Roti

Sunday, July 17, 2005

Manage Eclipse Plugin Directory

ปกติ plugin ของ eclipse จะเก็บอยู่ใต้
directory /INSTALL_DIR/eclipse/plugins
ซึ่งในสภาวะปกติการเก็บอยู่ในนั้นก็เพียงพอแก่การใช้งาน

แต่มีบางสถานการณ์ที่เราต้องการแยกพื้นที่
สำหรับ plugin ออกมาต่างหาก เช่น
เพื่อสะดวกในการจัดการ version ของ plugin
(ส่วนใหญ่เป็นสถานะการณ์ของการทดสอบ
การใช้งาน plugin project ที่อยู่ระหว่าง
develope ซึ่งมักจะมี dependency ค่อนข้าง
เฉพาะเจาะจง)
หรือเพื่อสะดวกในการ share plugin directory
ระหว่าง eclipse หลายๆ instance

เราสามารถ add Extension Location
ผ่านทาง Menu Help->Software updates->Manage Configuration
และเลือกหัวข้อ Add an Extension Location
ซึ่งสามารถระบุ directory ที่ต้องการได้



สำหรับ directory ที่จะใช้เป็น extension location
ได้นั้น ต้องมีเงื่อนไขดังนี้
  • มี direcotry structure ดังนี้

    eclipse
    |
    +-features
    |
    +-plugins

  • ภายใน directory eclipse
    ต้องมี file ที่ชื่อ .eclipseextension
    และมีเนื้อหาใน file ดังนี้
    id=org.eclipse.platform
    name=Eclipse Platform
    version=3.1.0

    (ผมไม่ได้ใช้ version 3.0.x แล้ว
    ก็เลยไม่ได้ทดลองว่าถ้าเป็น eclipse version 3.0.x
    จะต้องใส่เลข version เป็นอะไร)


ปัญหาที่เจอก็คือ พอเรา add
directory เข้าไปแล้ว
ปรากฎว่า มันไม่มี action
สำหรับการ remove extension location
ซึ่งเท่าที่ดู เราสามารถเข้าไป
edit file ที่ชื่อ platform.xml
ซึ่งอยู่ภายใต้ direcotry INSTALL_DIR/eclipse/configuration/org.eclipse.update
โดยสามารถลบ <site>
ที่ไม่ต้องการออกได้

Related link from Roti

Thursday, July 14, 2005

FaceAnalyzer

เป็น site ที่เปิดให้เรา submit หน้าเราเข้าไป
จากนั้นก็จะใช้ facial recognition technology
ทำการวิเคราะห์ personality ของเรา

เพื่อให้หายข้องใจ ก็เลยจัดการส่งรูปลูกสุดที่รักไปทดสอบ
ผลการวิเคราะห์ได้ออกมาดังนี้
face analyzer of pann

Race Analysis น่าเชื่อถือมาก
เพราะลูกผมมันหน้าตี๋ (ถึงหน้าพ่อมันจะเหมือนพวกทมิฬก็เถอะ)
ส่วน Gender นั้นทายผิด (อันนี้ก็น่าจะผิดอยู่หรอก
เพราะ คนทั่วไปเวลาเห็นหน้าลูกผม
ก็เกิดความไม่แน่ใจเหมือนกันว่า ผู้ชายหรือผู้หญิง)

ใครสนใจลองส่งรูปตัวเองเข้าประกวดก็ลองดู
(เดี๋ยวจะแอบส่งรูปแฟนเข้าวิเคราะห์สักหน่อย)

Related link from Roti

Wednesday, July 13, 2005

Request xml output from rails

วันนี้ได้ทดลองเขียน rails ให้ response เป็น xml content
สรุปวิธีการได้ดังนี้

โจทย์
เราจะลองสร้าง web application ที่ provide
ข้อมูล contact ให้กับผู้ร้องขอ โดยให้ format
กลับมาเป็น xml

  • สร้าง application
    โดยใช้คำสั่ง
    rails contactApp

    ซึ่งเป็นการสร้าง framework structure ขึ้นใน
    directory contactApp

  • สร้าง controller สำหรับรับ request

    ให้ cd ไปยัง directory contactApp ที่ rails generate ให้
    จากนั้นสั่งสร้าง controller
    ruby script/generate controller xml

    จะได้ file xml_controller.rb ใน directory app/controllers

  • implement action
    ในที่นี้จะลองสร้าง action ที่ชื่อ list
    ซึ่งทำหน้าที่แสดงรายการ contact ทั้งหมดกลับไปในรูป xml
    class XmlController < ApplicationController
    def list
    @contacts = Contact.find_all
    @headers["Content-Type"] = "text/xml; charset=utf-8"
    render :action=>'list', :layout => false
    end
    end

    step การทำงานก็คือ select ข้อมูล
    ทั้งหมดของ contact ขึ้นมาจาก database
    ใส่ไว้ใน class attribute ที่ชื่อ contacts

    จากนั้นก็ set content-type ของ response
    ให้เป็น text/xml

    สุดท้ายก็ dispatch ไป render โดยใช้ view ที่ชื่อ list
    โดยระบุไม่ให้ layout component ทำงาน
    (layout จะทำหน้าที่ generate ส่วนที่
    ใช้ซ้ำๆกันใน view เช่น พวก header, footer
    ของ page)

  • สร้าง view ที่ชื่อ list.rxml
    สังเกตุว่านามสกุลเป็น rxml เนื่องเป็น
    template ที่ช่วยในการ generate xml
    (template นี้ใช้ builder module
    ในการ generate xml)

    ภายใน list.rxml มีเนื้อหาดังนี้
    xml.instruct! :xml, :version=>"1.0"
    xml.contacts do
    @contacts.each do |contact|
    xml.contact(:name=>contact.name, :email=>contact.email)
    end
    end



ทดลองเรียกใช้งาน
โดยส่ง request ไปยัง url "http://host:port/xml/list"
(เป็นการเรียกใช้ action list ที่อยู่ใน xml controller)
ผลลัพท์ที่ได้
<?xml version="1.0" encoding="UTF-8"?>
<contacts>
<contact name="polawat phetra" Email="pphetra@x.hotmail.com"/>
<contact name="prapapan phetra" Email="cprapapan@y.yahoo.com"/>
<contact name="pann phetra" Email="pann@pokhome.com"/>
</contacts>

Related link from Roti

remove rails cache

วันนี้ upgrade Rails
ปรากฎว่าเกิด error
undefined method `discard' for {}:Hash

ค้น internet พบว่าต้อง clear session
โดยใช้คำสั่ง
sudo rm /tmp/ruby*

Related link from Roti

config pooling ใน Spring Framework

ส่วนใหญ่เรามัก implement service layer
(หรือ business logic layer)
ในลักษณะ Singleton ซึ่งหมายความว่า
method ของเรา implement เป็น Thread-safe

แต่มีบางครั้งที่ design ของเราออกมาในรูป
ของ non-thread-safe service object
ในกรณีเรามักจะ implement มันในรูปของ object pool
เพื่อลดเวลา create object (initial resources)
(รวมทั้งลด Object ที่ต้องถูก garbage collection ลงด้วย)

วิธีการ config pooling ใน Spring
สามารถทำผ่าน ProxyFactoryBean ได้
โดยการ set targetSource

สมมติเรามี Class NonThreadSafeService

public class NonThreadSafeService {
public void callMe() {
..
}
}


ขั้นแรกให้ define ใน spring configuration ดังนี้
<bean id="ntsService" class="NonThreadSafeService"
singleton="false"/>


จากนั้นก็ใช้ CommonsPoolTargetSource เข้ามาห่อไว้
<bean id="poolTargetSource" 
class="org.springframework.aop.target.CommonsPoolTargetSource">
<property name="targetBeanName"><value>ntsService</value></property>
<property name="maxSize"><value>25</value></property>
</bean>


สุดท้ายก็ห่อด้วย AOP Proxy
<bean id="nts"
class="org.sprfr.aop.framework.ProxyFactoryBean">
<property name="targetSource"
ref="poolTargetSource"/>
</bean>


ชอบตรงที่เป็นการแก้ไขที่ configuration เพียงที่เดียว
ก็ได้ feature pooling แล้ว
โดยไม่ต้องไปแตะ code ในส่วนที่เรียกใช้ service เลย

Related link from Roti

Good Experience Games

Signal vs. Noise แนะนำ site ที่น่าสนใจ
Good Experience Games
ลองเข้าไปเล่นดูแล้ว
มีแต่ game ที่น่าสนใจทั้งนั้น
เช่น
Planarity
อันนี้ชอบความ simple ของมัน

Loop
อันนี้ชอบ interface

Related link from Roti

Bézier curves

อ่านเจอเรื่องนี้ Bézier curves
It is attributed and named after a French engineer, Pierre Bézier, who used them for the body design of the Renault car in the 1970's. They have since obtained dominance in the typesetting industry and in particular with the Adobe Postscript and font products.


ก็เลยลอง implement ด้วย java ดู
/*
* DrawPanel.java
*
* Created on July 9, 2005, 9:12 AM
*
*/

package curve;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;

/**
*
* @author pphetra
*/
public class DrawPanel extends JPanel {
private static int LOOP_COUNT = 60;

protected void paintComponent(Graphics g) {
super.paintComponent(g);
ArrayList points = new ArrayList();
points.add(new Point2D.Double(0.0, 200.0));
points.add(new Point2D.Double(50.0, 50.0));
points.add(new Point2D.Double(100.0, 150.0));
points.add(new Point2D.Double(200.0, 0.0));

paintBazier(g, points, LOOP_COUNT);

paintControlPoints(g, points);

}

private void paintControlPoints(Graphics g, List points) {
g.setColor(Color.MAGENTA);
Point2D.Double oldp = null;
for (int i = 0; i < points.size(); i++) {
Point2D.Double p = (Point2D.Double) points.get(i);
if (oldp != null) {
g.drawLine((int) oldp.x, (int) oldp.y, (int) p.x, (int) p.y);
}
oldp = p;
}


}

private void paintBazier(Graphics g, List points, int loopCount) {
Point2D.Double oldp = null;

for (int i = 0; i < loopCount; i++) {
Point2D.Double p = bazier(points, points.size()-1, (double) i/ (doub
le) loopCount);
if (oldp == null) {
oldp = p;
}
g.drawLine((int) p.x, (int) p.y, (int) oldp.x, (int) oldp.y);
oldp = p;
}
Point2D.Double p = (Point2D.Double) points.get(points.size()-1);

g.drawLine((int) oldp.x, (int) oldp.y, (int) p.x, (int) p.y);
}

private Point2D.Double bazier(List points, int n, double mu) {

int k, kn, nn, nkn;
double blend, muk, munk;
Point2D.Double b = new Point2D.Double(0.0, 0.0);

muk = 1;
munk = java.lang.Math.pow(1-mu, n);

for (k = 0; k <= n; k++) {
nn = n;
kn = k;
nkn = n-k;
blend = muk * munk;
muk *= mu;
munk /= (1-mu);
while (nn >= 1) {
blend *= nn;
nn--;
if (kn > 1) {
blend /= (double) kn;
kn--;
}
if (nkn > 1) {
blend /= (double) nkn;
nkn--;
}

}
Point2D.Double p = (Point2D.Double) points.get(k);
b.x += (p.x * blend);
b.y += (p.y * blend);
}

return b;
}
}


ได้ผลลัพท์ดังนี้


ในโปรแกรม จะเห็นว่ามีค่า LOOP_COUNT อยู่ตัวหนึ่ง
ค่านี้ก็คือจำนวน line segment ที่ใช้ draw curve
ปัญหาก็คือ ควรใช้ค่าเท่าไรดี
น้อยไป ก็ไม่โค้ง
มากไป ก็เปลือง
ก็เลยมีบทความ Adaptive Subdivision of Bezier Curves

Related link from Roti

Monday, July 11, 2005

ทดลอง google map api ใน blogspot

วันนี้ผมทดลองสมัครเข้าใช้บริการ google map api
โดย google map api ยอมให้ใช้ api
เฉพาะใน web site ที่สมัครเข้าใช้งานเท่านั้น
อย่างของผม ก็สมัครใช้ api
กับ url http://pphetra.blogspot.com
ได้ key ยืดยาวมาชุดหนึ่ง

เริ่มแรก ก็คือเราต้อง include api ในส่วน header ของ web page

<script src="http://maps.google.com/maps?file=api&v=1&key=abcdefg" type="text/ja
vascript"></script>

จากนั้นก็กำหนดบริเวณ render map ด้วย div block

<div id="mymap" style="width: 400px; height: 400px"></div>


ทำการ new GMap Object ขึ้นมาด้วย javascript
if (GBrowserIsCompatible()) { 
var map = new GMap(document.getElementById("mymap"));
map.addControl(new GSmallMapControl());
map.addControl(new GMapTypeControl());
map.setMapType(_SATELLITE_TYPE);
map.centerAndZoom(new GPoint(99.003489, 18.788563), 1);
}

Note: ค่า map type _SATELLITE_TYPE นั้นไม่ตรง
กับ api spec ซึ่งมีค่าเป็น G_SATELLITE_TYPE
ลองใช้ G_SATELLITE_TYPE แล้วทำงานไม่ถูกต้อง

ตัวอย่าง GPoint ที่ทดลองให้ดูนี้เป็นค่า latitude, longitude ของเมืองเขียงใหม่
บริเวณตลาดวโรรส
ถ้าใช้ google map ดู ก็จะมี url แบบนี้
http://maps.google.com/maps?ll=18.788563,99.003489&spn=0.008898,0.005922&t=k&hl=en

Note: ค่า latitude, longitude ใน url กับใน constructor ของ GPoint เรียงลำดับต่างกัน

จะเห็นได้ว่าวิธีใช้ google map Api นั้นง่ายๆ
แต่ประเด็นปัญหาก็คือ
เราไม่สามารถใส่ javascript
ลงไปใน post ของเราได้
(editor ของ blogger จะไม่ยอมให้เราใส่ tag javascript)
ก็เลยเดือนร้อนต้องหาวิธีการ embed javascirpt
ลงไปตอน post ให้ได้

วิธีการที่ใช้ก็คือ ห่อ javascript ที่ต้องการ
ลงไปใน div tag (โดยกำหนด style เป็น display:none)
จากนั้น ณ จังหวะที่ onload ของ page
ก็จัดการ scan หา div tag นัั้นๆ
แล้วก็สั่ง eval เจ้า javascript เสีย
function embedjs() {
var xs = document.getElementsByTagName("div");
var k = xs.length;
var maps = new Array(12);
var j = 0;
for (var i = 0; i < k; i++) {
if (xs[i].getAttribute("id") == "pokmap") {
maps[j++] = xs[i].innerHTML;
}
}

for (var i = 0; i < j; i++) {
eval(maps[i]);
}
}

Note : ให้ระวังอย่ายุบรวม for loop
scan กับ for loop evel เข้าด้วยกัน
เพราะว่าเมื่อเราเรียกเจ้า google map api
จะเกิดการ insert div ใหม่เข้ามา ผลลัพท์ก็คือ
จะเกิดวน loop ไม่รู้จบ

แก้ไขส่วน template เพิ่ม onload event
<body onload="embedjs()">


จากนั้นเวลา post ก็สามารถใส่ tag
ได้ดังนี้
<div id="123map" style="width: 400px; height: 400px">
</div>
<div id="pokmap" style="display: none;">if (GBrowserIsCompatible()) { var map =
new GMap(document.getElementById("123map")); map.addControl(new GSmallMapContro
l()); map.addControl(new GMapTypeControl()); map.setMapType(_SATELLITE_TYPE); ma
p.centerAndZoom(new GPoint(99.003489, 18.788563), 1);}</div>

ผลลัพท์ที่ได้ก็เป็นเช่นนี้


Related link from Roti

google map mania

ตั้งแต่รู้จาก คุณ Hui ว่า google map สามารถแสดง
ภาพของกรุงเทพได้แล้ว ก็เลยเกิดความสนุกในการใช้งาน

วันนี้เจอ link นี้เข้า
Google Maps Mania
เป็น blog ที่รวมสารพัดเรื่องที่เกี่ยวข้องกับ google map

มีบางอันเห็นแล้วอมยิ้ม
เช่น Scavengeroogle
เปิดให้เล่นเกมส์ตามล่า สถานที่ที่ post ว่าอยู่ที่ไหน

Related link from Roti

บทความน่าสนใจ

เห็นใน blog ของคนอื่นลง link ไว้ (เจอ 2 blog แล้วที่พูดถึงเรื่องนี้)
ก็เลยตามไปอ่านดู ค่อนข้างยาว
แต่พอจะอ่านข้ามได้
เพราะหลักการค่อนข้างเป็นที่รู้กันอยู่

Unskilled and Unaware of It: How Difficulties in Recognizing One's Own Incompetence Lead to Inflated Self-Assessments

quote มาให้ดูบางส่วนแล้วกัน
(เป็นหนูทดลองให้ bact เขาหน่อย
ว่า การ quote ถือเป็นการตัวแทนของ
ใจความสำคัญของเนื้อหาได้หรือไม่)
People tend to hold overly favorable views of their abilities in many social and intellectual domains.
Across 4 studies, the authors found that participants scoring in the bottom quartile on tests of humor, grammar, and logic grossly overestimated their test performance and ability. Although their test scores put them in the 12th percentile, they estimated themselves to be in the 62nd.


"above-average effect,"
the tendency of the average person to believe he or she is above average.
For example, high school students tend to see themselves as having more ability in leadership, getting along with others, and written expression than their peers
, business managers view themselves as more able than the typical manager
, and football players see themselves as more savvy in "football sense" than their teammates


"ignorance more frequently begets confidence than does knowledge"
Charles Darwin


ปล. เรื่องนี้สามารถ apply เข้ากับบรรดาท่านผู้ใหญ่ในบ้านเมืองนี้
ได้หลายท่านเลย

Related link from Roti

Friday, July 08, 2005

Code Snippets by tag

วันนี้เปิดเจอ site หนึ่งน่าสนใจดี
เป็นที่รวม code snippet
คล้ายๆกับ del.icio.us ที่ assign tag กับ url
อันนี้เปลี่ยนเป็น assign tag กับ code snippet แทน

ตัว application เขียนด้วย ruby on rails
แล้วก็เปิดให้ download ได้ด้วย

Link

Related link from Roti

Custom Schema Mapping in ActiveRecord (Rails)

ActiveRecord คือ module ที่ทำหน้าที่
Object/relation mapping ใน RubyOnRails Framework
วิธีการที่เขาใช้ จะใช้ reflection เป็นหลัก
และก็มี assumption บางอย่างที่ใช้ในการ
map column name, table name เข้ากับ Object

ตัวอย่างแรกสุดเลย
สมมติเราจะเขียน blog ด้วย Rails
ในแง่ของตัว relational database
เราก็ต้องมี table สำหรับเก็บ blog post
assumption ของ active record ก็คือ
  • ต้องมี column "id" เป็น primary key
  • ถ้ามี column created_on กับ updated_on
    เจ้า ActiveRecord จะดูแลการ set ค่าพวกนี้ให้โดยอัตโนมัติ
  • ชื่อ table ต้องอยู่ในรูปพหูพจน์


ดังนั้นเจ้า table blog post ของเราก็เลยออกมา
หน้าตาประมาณนี้
create table blog_posts (
id serial,
post text,
created_on timestamp,
primary key (id)
);


ส่วนเจ้า class BlogPost
ก็หน้าตาประมาณนี้
class BlogPost < ActiveRecord::Base
end

จะเห็นได้ว่าไม่ต้องระบุอะไรเลย
เดี๋ยว Reflection จัดให้เอง ตอน runtime

ส่วนการใช้งาน ก็เช่น
blogpost = BlogPost.new
blogpost.post = "hello world"
blogpost.save


ในกรณีที่เราเขียน app โดยออกแบบใหม่หมด
ตั้งแต่ table เลย ก็คงไม่มีปัญหาอะไร
แต่ถ้าเป็น Web Application ที่ต้องใช้
legacy table จากระบบเก่า
ก็จะเกิดปัญหาเรื่อง naming หรือ mapping ขึ้น
(แค่ชื่อ table ก็เป็นปัญหาแล้ว
บางคนเขานิยมตั้งชื่อ table เป็นรหัส)

ดังนั้นใน post วันนี้ เราจะลองมาดูว่า
เราสามารถ override assumption ต่างๆของ ActiveRecord
ได้อย่างไรบ้าง

เริ่มด้วยชื่อ table
ค้นเจอ 2 แบบ
class MyClassName < ActiveRecord::Base
def self.table_name() "my_table_name" end
end


class Mouse < ActiveRecord::Base
set_table_name "mice"
end

คืออันแรกใช้วิธี override method table_name()
ส่วนอันที่ 2 ใช้วิธี call method set_table_name("name")

กรณี primary key ของเราไม่ได้ชื่อ "id"
class MyClass < ActiveRecord::Base
set_primary_key "my_pk_name"
end


ส่วนกรณีที่เราต้องการชื่อ attribute
ที่ไม่ตรงกับชื่อ column
เท่าที่ค้นดู ยังไม่มีพูดถึงประเด็นนี้
แต่ถ้าทำด้วยวิธี object style ก็คงใช้วิธีเขียน method set, get ด้วยชื่อใหม่
แล้วข้างไนก็ค่อยไปเรียก set, get ด้วยชื่อจริง
class Song < ActiveRecord::Base

def length=(minutes)
write_attribute(:len, minutes)
end

def length
read_attribute(:len)
end
end

Related link from Roti

Thursday, July 07, 2005

Syntax Highligh Java source in Apache Forrest

เป้าหมายที่ต้องการทำก็คือ
ต้องการให้ Apache Forrest Application สามารถ
transform xdoc ในส่วนของ java source
ให้อยู่ในรูปสีสรรสดสวย
(เดิมใน xdoc มี tag ที่ชื่อ source
อยู่แล้ว แต่ไม่ได้ทำ highlight ให้)

วิธีการที่ทำได้มีอยู่ 2 ทางหลักๆ
  • Customize Project 's Pipeline
  • Customize Forrest 's Pipeline ที่ forrest installation directory โดยตรง


วิธีแรกดูดีสุด
โดยในคู่มือ forrest บอกไว้ว่า เราสามารถ
customize pipeline processing เฉพาะใน Document Project ของเรา
ผ่านทาง /src/documentation/sitemap.xmap ได้
โดยทำได้ 2 แบบคือ
  • render เองหมดเลย
  • render เฉพาะ body ที่เหลือปล่อยให้ forrest apply skin ให้

  • พยายามจะทำแบบที่ 2 แต่ไม่ได้สักที
    ก็เลยเปลี่ยนใจ ย้ายไป customize ที่ตัว
    forrest configuration โดยตรงเลย

    เริ่มจาก file /$FORREST_HOME/main/webapp/sitemap.xmap
    มันจะมีการ mapping url ไว้ดังนี้
    <map:match pattern="*.html">
    <map:aggregate element="site">
    <map:part src="cocoon:/skinconf.xml"/>
    <map:part src="cocoon:/build-info"/>
    <map:part src="cocoon:/tab-{0}"/>
    <map:part src="cocoon:/menu-{0}"/>
    <map:part src="cocoon:/body-{0}"/>
    </map:aggregate>

    <map:call resource="skinit">
    <map:parameter name="type" value="site2xhtml"/>
    <map:parameter name="path" value="{0}"/>
    </map:call>
    </map:match>


    นั่นคือหลังจากได้ url ที่ลงท้ายด้วย .html เข้ามา
    เจ้า forrest ก็จะแตกออกเป็น 5 dispatch ย่อย ที่จะ
    แจกไปให้แต่ละส่วนทำงาน เช่น ส่วน tab, ส่วน menu
    จากนั้นค่อย aggregate รวมกันแล้วส่งไป render อีกที

    กรณีของเรา ก็คือต้องการ customize ส่วน body
    จากของเดิมที่เป็นดังนี้
    <map:match pattern="**body-*.html">
    <map:generate src="cocoon:/{1}{2}.xml"/>
    <map:transform type="idgen"/>
    <map:transform type="xinclude"/>
    <map:transform type="linkrewriter" src="cocoon:/{1}linkmap-{2}.html"/>
    <map:transform src="resources/stylesheets/declare-broken-site-links.xsl" />
    <map:call resource="skinit">
    <map:parameter name="type" value="document2html"/>
    <map:parameter name="path" value="{1}{2}.html"/>
    <map:parameter name="notoc" value="false"/>
    </map:call>
    </map:match>


    โดยสิ่งที่เราจะเพิ่มเข้าไปก็คือ หลังจากที่ generator ได้ file xdoc
    มาแล้ว เราจะเข้าไป intercept sax event
    เพื่อที่ว่าจะได้ทำการ parse java source ให้อยู่ในรูป
    xml เสียก่อน จากนั้นก็จะส่งผ่านไปให้ xslt
    ทำการ render ให้อยู่ในรูปแบบที่ต้องการ

    <map:match pattern="**body-*.html">
    <map:generate src="cocoon:/{1}{2}.xml"/>
    <!-- parse java source to xml form -->
    <map:transform type="java2html" />
    <map:transform type="idgen"/>
    <map:transform type="xinclude"/>
    <map:transform type="linkrewriter" src="cocoon:/{1}linkmap-{2}.html"/>
    <!-- render xml syntax tree to html tag -->
    <map:transform src="resources/stylesheets/javahighlight.xsl"/>
    <map:transform src="resources/stylesheets/declare-broken-site-links.xsl" />
    <map:call resource="skinit">
    <map:parameter name="type" value="document2html"/>
    <map:parameter name="path" value="{1}{2}.html"/>
    <map:parameter name="notoc" value="false"/>
    </map:call>
    </map:match>


    ตัว transform type "java2html" จะทำหน้าที่แปลง
    javasource ให้อยู่ในรูป xml โดยมี tag แปะตาม
    ประเภทของ token
    เช่น
    package pok.test;
    import java.util.ArrayList;

    ก็จะถูกแปลงเป็น
    <javakeyword>package</javakeyword pok.test;
    <javakeyword>import</javakeyword> java.util.<java_type>ArrayList</java_type>;


    ส่วน transform อีกตัวจะทำหน้าที่ apply style sheet "javahighlight.xsl"
    เพื่อ render ให้อยู่ในรูปแบบ html ที่ต้องการ
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
    <xsl:apply-templates/>
    </xsl:template>

    <!-- the obligatory copy-everything -->
    <xsl:template match="node() | @*">
    <xsl:copy>
    <xsl:apply-templates select="@*"/>
    <xsl:apply-templates/>
    </xsl:copy>
    </xsl:template>

    <xsl:template match="jshl">
    <pre class="hl">
    <xsl:apply-templates mode="javasource"/>
    </pre>
    </xsl:template>

    <xsl:template match="java_plain" mode="javasource">
    <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="java_keyword" mode="javasource">
    <span class="S_KEYWORD1"><xsl:apply-templates/></span>
    </xsl:template>
    ...


    วิธีการ implement transformer
    เขียน class Java2HtmlTransformer โดย extends AbstractSaxTransformer
    package pok.cocoon.transformer;



    import java.io.StringBufferInputStream;

    import org.apache.cocoon.transformation.AbstractSAXTransformer;
    import org.apache.cocoon.xml.AttributesImpl;
    import org.xml.sax.Attributes;
    import org.xml.sax.SAXException;



    public class Java2HtmlTransformer extends AbstractSAXTransformer {

    private static final String TAG_NAME = "javasource";

    public void endElement(String uri, String name, String raw)
    throws SAXException {
    if (name.equals(TAG_NAME)) {



    String srcStr = this.endTextRecording();
    JavaXmlRenderer render = new JavaXmlRenderer();
    sendStartElementEvent("jshl");
    try {

    render.highlight(new StringBufferInputStream(srcStr), this);

    } catch (Throwable e) {
    e.printStackTrace();
    }
    sendEndElementEvent("jshl");

    } else {
    super.endElement(uri, name, raw);
    }

    }

    public void startElement(String uri, String name, String raw,
    Attributes attr) throws SAXException {

    if (name.equals(TAG_NAME)) {
    this.startTextRecording();
    } else {

    super.startElement(uri, name, raw, attr);
    }
    }

    }

    หลังจากเขียน class แล้ว เราต้องบอกให้ Forrest
    รู้จัก transformer นี้โดยการเพิ่ม config เข้าไปใน sitemap.xmap
    <map:transformers>
    <map:transformer name="java2html"
    src="pok.cocoon.transformer.Java2HtmlTransformer"
    logger="pok"
    />
    </map:transformers>


    ลักษณะการทำงานของ Java2HtmlTransformer ก็คือเราจะสนใจ sax event
    เฉพาะส่วน startElement กับ endElement
    กรณีที่พบ tag <javasource> เราก็จะทำการ
    record text ที่เกิดขึ้นระหว่าง tag เปิด กับ tag ปิด
    และทำการส่งไปให้ JavaXmlRenderer ทำการ parse text ในส่วนนี้

    ส่วน Class JavaXmlRenderer ใช้ source code ตัวอย่างจาก
    project JHighlight
    package pok.cocoon.transformer;

    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.Reader;
    import java.io.StringReader;

    import org.apache.cocoon.transformation.AbstractSAXTransformer;

    import com.uwyn.jhighlight.highlighter.JavaHighlighter;
    import com.uwyn.jhighlight.tools.StringUtils;

    public class JavaXmlRenderer {



    public void highlight(InputStream in, AbstractSAXTransformer sax) throws Exc
    eption {

    JavaHighlighter highlighter = new JavaHighlighter();
    highlighter.ASSERT_IS_KEYWORD = true;

    Reader isr;
    isr = new InputStreamReader(in);


    BufferedReader r = new BufferedReader(isr);


    String line;
    String token;
    int length;
    int style;
    String css_class;
    String previous_class = null;
    int previous_style = 0;
    while ((line = r.readLine()) != null)
    {
    line += "\n";
    line = StringUtils.convertTabsToSpaces(line, 4);

    // should be optimized by reusing a custom LineReader class
    Reader lineReader = new StringReader(line);
    highlighter.setReader(lineReader);
    int index = 0;
    while (index < line.length())
    {
    style = highlighter.getNextToken();
    length = highlighter.getTokenLength();
    token = line.substring(index, index + length);

    if (style != previous_style)
    {
    css_class = getCssClass(style);

    if (css_class != null)
    {
    if (previous_class != null)
    {
    sax.sendEndElementEvent(previous_class);
    }
    sax.sendStartElementEvent(css_class);

    previous_class = css_class;
    previous_style = style;
    }
    }
    sax.sendTextEvent(token);

    index += length;
    }

    }
    }


    protected String getCssClass(int style)
    {
    switch (style)
    {
    case JavaHighlighter.PLAIN_STYLE:
    return "java_plain";
    case JavaHighlighter.KEYWORD_STYLE:
    return "java_keyword";
    case JavaHighlighter.TYPE_STYLE:
    return "java_type";
    case JavaHighlighter.OPERATOR_STYLE:
    return "java_operator";
    case JavaHighlighter.SEPARATOR_STYLE:
    return "java_separator";
    case JavaHighlighter.LITERAL_STYLE:
    return "java_literal";
    case JavaHighlighter.JAVA_COMMENT_STYLE:
    return "java_comment";
    case JavaHighlighter.JAVADOC_COMMENT_STYLE:
    return "java_javadoc_comment";
    case JavaHighlighter.JAVADOC_TAG_STYLE:
    return "java_javadoc_tag";
    }
    return null;
    }

    }


    โดยหลักการทำงาน ก็คือ JavaXmlRenderer จะเรียกใช้ scanner ที่ชื่อ
    JavaHighlighter (implement ด้วย Flex)
    ทำการ scan ทีละบรรทัด
    ผลลัพท์ที่ได้ จะได้ style กับ token ออกมา
    จากนั้น ก็จะทำการส่ง sax event ออกไป
    โดยมี tag name ตามรูปแบบ style ที่ได้มาจาก scanner

    ตัวอย่างการนำไปใช้
    <javasource>
    package pok.test;
    import java.util.ArrayList;

    /**
    * this is <code>test</code>
    * @param hi
    */
    public class Main implements java.io.Serializable {
    public static void main(String[] args) {
    System.out.println("helloWorld");
    if (1 != 2) {
    if (1 > 3) {
    // pok test
    }
    }
    }
    }
    </javasource>

    ผลลัพท์ที่ได้


    สรุป ปัญหาของการ customize forrest หรือ cocoon framework ก็คือ
    เกิดจากเจ้าตัว cocoon framework ที่ค่อนข้างจะซับซ้อนพอสมควร
    แถมมันยังใช้ IOC container ที่ชื่อ Avalon Excalibur
    ยิ่งทำให้การทำความเข้าใจ source code ต่างๆ ต้องใช้เวลามาก
    นอกจากนี้ ยังมีประเด็นความเข้าใจในส่วนของการ parse xml Document
    ที่มักจะมีปัญหาเรื่อง namespace เสมอ

    Related link from Roti

    Tuesday, July 05, 2005

    window1.01, 2.0 screenshots

    พึ่งเคยเห็น screen shot ของ window1.01 กับ 2.0
    ดู classic ดีจัง

    Link

    Related link from Roti

    Sunday, July 03, 2005

    ยืนด้วยครับ

    เมื่อวานออกไปทำธุระนอกบ้้าน
    เดินไปถึงหน้าปากซอย
    พบตำรวจกำลังปิดถนน
    รอขบวนเสด็จ
    ก็เลยขี้นิ้วไปที่สะพานลอย
    (ทำนองว่า ข้ามได้ไหม)
    ตำรวจก็บอก เดี๋ยวก่อน
    ก็เลยไปนั่งรอข้างๆสะพานลอย
    (เปิดอิฐก่อสูงประมาณ ฟุตครึ่ง)
    ตำรวจมอง แล้วก็บอกว่า "ช่วยยืนด้วยครับ"

    รู้สึกจี๊ดขึ้นมาที่หัวเลย
    แต่ก็ยังสงบอาการ
    บอกตำรวจไปว่า
    "เดี๋ยวมาแล้วค่อยยืน"

    พอขบวนมาถึง
    ตำรวจทำท่ายืน ระเบียบพัก
    แล้วก็พูดอีก "ยืนด้วยครับ"
    คราวนี้ ก็เลยส่ายหัว แล้วก็บอกว่า "ไม่ยืน"
    (นึกในใจ ทำไมกูต้องยืนด้วยวะ)

    จบ
    ปล. เจอเหตุการณ์นี้แล้วนึกถึง
    ตอนไปเที่ยวพระที่นั่งวิมาณเมฆ
    สวยดีนะ เสียอย่างเดียว พอ guide (พวกราชการ)
    นำชมมาถึงห้องสุดท้าย
    guide ก็บอกว่า ทุกคนก้มลงกราบพระรูปพร้อมกันด้วยค่ะ
    แถมมีบทพูดถวายบังคม ที่ต้องพูดพร้อมกันด้วยนะ

    ปล. ในเชิงวัฒนธรรมแล้ว
    ถือได้ว่าเป็นเรื่องเดียวกับ
    เรื่องรุ่นน้องต้องเคารพรุ่นพี่
    (หรือรุ่นพี่สั่งยืนแถว)
    ในตอนรับน้องไหมเนี่ย

    Related link from Roti

    Friday, July 01, 2005

    Flash Demo: Netbeans + J2EE1.5 + EJB3.0 + GlassFish

    Demo
    ดูแล้วน่าสนใจยิ่ง

    ยังไม่ได้อ่าน spec ของ EJB 3.0 เลย
    แต่คุ้นๆว่ามีการนำ annotation มาใช้เยอะมาก

    ใน flash demo มีตัวอย่าง
    ของ servlet เรียกใช้ session bean
    ชอบตรง annotation ที่ทำหน้าที่
    คล้ายๆ Injection
    (container จะรู้เองว่าควรเอา
    object หรืออะไร มา set ให้)

    Note:
    GlassFish คือ Opensource
    Application Server ของ Sun
    (เดิมคือ Sun Applicatoin Server 9
    ซึ่งให้ใช้ฟรีใน version PE )

    Related link from Roti

    Eclipse Bug Report in verse

    Eclipse Bug number 100918

    Shade Of Grey

    Lyrics: mvm
    Music: sung to the tune of the hit by "The Grateful Dead"

    I guess it's just a shade of grey
    When my editor dropdown opens I might realize
    Just might realize
    But those grey stripes
    That easy slice
    Gonna stay with me a while

    But out in Motif
    Well it's like a tractor factory
    Out in the streets
    They just don't use yellow

    Bout seven list items high
    All their dreams have been realized
    Are being verticalized
    And those grey strips
    Just light up and shine
    When I hit ctrl+e or touch that mouse of mine

    etc...

    Related link from Roti

    Wednesday, June 29, 2005

    Jdk1.5 & "Revenge of the Sith"

    จาก Kirill Grouchnikov's Blog
    The real story about how Anakin Skywalker became Darth Vader

    ใครลองทำ "บ้านทรายทอง" บ้างสิ

    Related link from Roti

    Art of Science Competition

    at princeton university
    Link

    "dynamic asset allocation problem in railroads"

    "Transgenic Neural Stem Cells"

    Related link from Roti

    Monday, June 27, 2005

    Forrest (Publishing framework)

    ช่วงนี้นั่งทำเอกสาร tutorial อยู่
    ก็เลยมองหา tool มาช่วยเขียน
    จำได้ว่าพวก Apache ใช้มาตรฐาน xdoc
    ในการทำเอกสาร ก็เลยตามเข้าไปดู

    ไปเจอโปรเจค Forrest เข้า
    Apache Forrest is a publishing framework that
    transforms input from various sources
    into a unified presentation
    in one or more output formats

    ก็เลย load มาทดลองใช้ดู

    การใช้งาน ก็ง่ายดี
    เริ่มต้นด้วยการสร้าง project ใหม่
    ด้วยคำสั่ง forrest seed
    โดยเจ้า forrest จะสร้าง directory, template file
    ต่างๆให้



    จากนั้นเราก็เข้าไป edit เอกสาร
    ซึ่งใช้ format xdoc
    และถ้าต้องการ generate web site ก็เพียงแต่สั่ง
    forrest run เจ้า forrest ก็จะจัดการ
    generate ให้เรียบร้อย

    นอกจากนี้ยังสามารถ start เป็น web application ได้ด้วย
    โดยสั่ง forrest run
    เจ้า forrest ก็จะทำการ start jetty ให้
    บน port 8888 ทำให้เราสามารถแก้ไขเอกสาร
    พร้อมกับดูผลลัพท์ได้ทันที

    หลังจากลองใช้สักพัก ก็เริ่มโลภมาก
    อยากได้ feature source highlight
    โดยกะว่าจะเพิ่ม tag พิเศษที่ชื่อ <javacode>
    จะได้เอาไว้ render source code ให้มีสีสรรสวยงาม
    ก็เลยค้นเอกสาร forrest
    จึงรู้ว่า เจ้า forrest ใช้ cocoon เป็นฐานราก
    Cocoon implements these concepts around
    the notion of 'component pipelines',
    each component on the pipeline
    specializing on a particular operation.
    This makes it possible to use a Lego(tm)-like approach
    in building web solutions,
    hooking together components into pipelines
    without any required programming.

    ใน pipeline ของ cocoon จะมี component
    อยู่ 3 แบบก็คือ
    • generator ทำหน้าที่สร้าง xml sax event
      ขึ้นจาก source ต่างๆ ไม่ว่าจะเป็น file system
      ,jdbc, xml database, etc..
    • transformer ทำหน้าที่ตามชื่อ
      โดยรับ input เป็น sax event
      และให้ output เป็น sax event
    • Serializer ทำหน้าที่แปลง
      sax event ออกเป็น final format
      ซึ่งอาจจะเป็น html, pdf, svg, office document, etc..

    forrests เปิดโอกาสให้เรา customize pipeline ได้
    ก็เลยกะว่าจะ implements เจ้า Java2HtmlTransformer
    ขึ้นมาเอง และก็ add เข้าไปใน pipeline ของ forrest เสีย
    โดยเอา library java2html มาใช้

    Related link from Roti

    del.icio.us direc.tor

    feature ใหม่ ของ del.icio.us
    ชื่อ direc.tor
    เป็นพวก AJAX อีกแล้ว
    ที่น่าสนใจก็คือ เขาเขียน "How does it work?"
    อธิบายการทำงานไว้ด้วย

    Related link from Roti

    The NetBeans Collaboration Project

    Sun ปล่อย opensource ออกมาอีกตัวแล้ว
    คราวนี้เป็น NetBeans Collaboration Project
    ซึ่งเดิมเป็น feature ใน Sun Java Studio Enterprise 7
    ซึ่งลักษณะการทำงาน ก็เป็นพวก IM
    ที่เปิดโอกาสให้ developer chat กันได้
    โดยผ่าน collaboration service ที่ java.net
    ให้บริการอยู่

    ได้ดู flash demo แล้ว
    ชอบ feature ในส่วน share code
    ที่เปิดโอกาสให้ร่วมกันแก้ไข source code กันได้พร้อมๆกัน
    โดยระหว่างแก้ source code ของอีกฝั่ง
    จะเปลี่ยนเป็น guard box ไว้ (lock เพื่อป้องกัน conflict)
    จนกว่าจะแก้เสร็จ (หยุดพิมพ์)

    Related link from Roti