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