顯示具有 Java 標籤的文章。 顯示所有文章
顯示具有 Java 標籤的文章。 顯示所有文章

2010年1月22日 星期五

再會了~昇陽 So long, old friend

歐盟今天通過了Oracle與Sun的併購案,也等於宣告Sun這個偉大公司的離去~
Java之父James Gosling在他的部落格貼出一幅畫,應該非常貼切許多Java人(或者是傾向Open Source程式開發者)的心聲...


2009年1月15日 星期四

去除UTF-8 BOM表頭的InputStreamReader

如果你曾經使用Java遇過讀一些文字檔前面出現??等亂碼的話, 表示該份文件有加入bom(byte order mark), 基本上bom的用途原本是用來讓程式辨別該份文檔的編碼格式, Microsoft在Windows 2000以後的Notepad存UTF-8的檔案會加上 BOM(Byte Order Mark, U+FEFF), 主要是因為UTF-8和ASCII是相容的, 為了避免使用者自己忘記用甚麼存, 造成UTF-8檔案用 ASCII 模式開看到是亂碼, 所以在檔案最前面加上BOM.

看到這裡可能有很多人會開始%#~!微軟...

但是碰到問題總是得解決的, 下面這個改寫InputStreamReader的UnicodeInputStreamReader就能夠讓你免去上述煩惱, 所有功能仍然和InputStreamReader相同, 只是在constructor中加入判斷bom表頭的機制, 使用PushbackInputStream當讀取到相關格式時能夠移動檔案開始位置.

使用方式: BufferedReader in = new BufferedReader(new UnicodeInputStreamReader(new FileInputStream(file), encode));

UnicodeInputStreamReader:

package com.progking.io;

import java.io.IOException;
import java.io.PushbackInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import sun.nio.cs.StreamDecoder;

public class UnicodeInputStreamReader extends InputStreamReader
{
   private static final int BOM_SIZE = 4;
   private final StreamDecoder decoder;
   private PushbackInputStream pushBack;
   private String encode;
   private String defaultEnc;

   public UnicodeInputStreamReader(InputStream input, String defaultEnc) throws UnsupportedEncodingException
   {
       super(input);
       try {
           this.defaultEnc = defaultEnc;
           pushBack = new PushbackInputStream(input, BOM_SIZE);
           init();
       } catch (Exception e) {
           e.printStackTrace();
       }
       decoder = StreamDecoder.forInputStreamReader(pushBack, this, encode);
   }

   private void init() throws IOException
   {
       byte[] bom = new byte[BOM_SIZE];
       int n, unread;

       // 初始讀取一次
       n = pushBack.read(bom, 0, bom.length);

       // 比對表頭
       if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE) && (bom[3] == (byte) 0xFF)) {
           encode = "UTF-32BE";
           unread = n - 4;
       } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE) && (bom[2] == (byte) 0x00) && (bom[3] == (byte) 0x00)) {
           encode = "UTF-32LE";
           unread = n - 4;
       } else if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
           encode = "UTF-8";
           unread = n - 3;
       } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
           encode = "UTF-16BE";
           unread = n - 2;
       } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
           encode = "UTF-16LE";
           unread = n - 2;
       } else {
           // 如果沒有找到任何表頭, 則退回長度等於原先總長
           encode = defaultEnc;
           unread = n;
       }
       System.out.println("has BOM=" + ((unread == n) ? false : true) + ", encode=" + encode + ", read=" + n + ", unread=" + unread);
       // 計算應該退回多少byte
       if (unread > 0)
           pushBack.unread(bom, (n - unread), unread);
   }

   public String getEncoding()
   {
       return encode;
   }

   public int read() throws IOException
   {
       return decoder.read();
   }

   public int read(char cbuf[], int offset, int length) throws IOException
   {
       return decoder.read(cbuf, offset, length);
   }

   public boolean ready() throws IOException
   {
       return decoder.ready();
   }

   public void close() throws IOException
   {
       decoder.close();
   }
}

2008年12月4日 星期四

Java FunP 自動推文程式

測試了一個禮拜應該是沒有問題了, 發佈出來讓大家參考, 使用此程式先要有apache HttpClient相關套件(請參閱之前發文運用Apache HttpClient實作Get與Post動作)

流程為: 登入FunP之後記住Cookie之外, 進入其他頁面必須還要傳送not_rem_login與expires等驗證資訊, 成功取得頁面內容後使用Regular Expression取得文章代號, 就可以進行推文與收藏的動作, 如果想自己執行網頁流程看看傳輸內容的話建議使用SmartSniff, 可以看見POST或GET傳輸內容與表頭。

FunpAutoLogin:

package org.sam.funp;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;

public class FunpAutoLogin
{
    private HttpClient client;
    private FunpPusher pusher;
    private FunpAddFavo addfavo;

    public FunpAutoLogin()
    {
        pusher = new FunpPusher();
        addfavo = new FunpAddFavo();

        // set up the HttpClient object.
        client = new HttpClient();
        // host configuration.
        client.getHostConfiguration().setHost("funp.com", 80, "http");
    }

    public void login()
    {
        PostMethod method = new PostMethod("/account/login.ajax.php");

        // Cookie設置.
        Header newCookieHeader = method.getResponseHeader("Set-Cookie");
        Header currentCookieHeader = method.getRequestHeader("Cookie");
        if (newCookieHeader != null) {
            if (currentCookieHeader == null) {
                method.setRequestHeader("Cookie", newCookieHeader.getValue());
            } else {
                method.setRequestHeader("Cookie", currentCookieHeader.getValue() + "; " + newCookieHeader.getValue());
            }
        }
        // 表頭設置.
        method.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.4.154.25 Safari/525.19");  
        method.setRequestHeader("Referer", "http://funp.com/push/");  
        method.setRequestHeader("Cache-Control", "max-age=0");  
        method.setRequestHeader("X-Prototype-Version", "1.5.1.1");  
        method.setRequestHeader("X-Requested-With", "XMLHttpRequest");  
        method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");  
        method.setRequestHeader("Accept", "text/javascript, text/html, application/xml, text/xml, */*");  
        method.setRequestHeader("Accept-Encoding", "gzip,deflate,bzip2,sdch");  
        method.setRequestHeader("X-SDCH", "Chrome 0.4.154.25");  
        method.setRequestHeader("Accept-Language", "zh-TW,zh,en-US,en");  
        method.setRequestHeader("Accept-Charset", "Big5,*,utf-8");  
        method.setRequestHeader("Host", "funp.com");  
        method.setRequestHeader("Content-Length", "69");  
        method.setRequestHeader("Connection", "Keep-Alive");  
    
        // POST傳送資訊.
        NameValuePair login = new NameValuePair("titlebar_login", "");  
        NameValuePair email = new NameValuePair("titlebar_id", your account);  
        NameValuePair passwd = new NameValuePair("titlebar_password", your passwd);  
        NameValuePair no = new NameValuePair("_", "");  
        method.setRequestBody(new NameValuePair[] { login, email, passwd, no });  
  
        try {  
            client.executeMethod(method);  
     
            // get response status.
            int state = method.getStatusCode();  
            if (state == HttpStatus.SC_OK) {  
                System.err.println("> login success.");  
                method.releaseConnection();  
      
                // 每頁推完休息120秒.
                for(int i = 1; i <= 4; i++){  
                    System.err.println("> browsing page " + i + ".");  
                    getPushDetail(1, 1, i);  
                    try{  
                        Thread.sleep(120000);  
                    }catch(Exception e){  
                        e.printStackTrace();  
                    }  
                }  
            } else {  
                System.err.println("> login failed, funP system might be crashed.");  
            }  
        } catch (HttpException httpexc) {  
            System.err.println(">> fatal protocol violation: " + httpexc.getMessage());  
            httpexc.printStackTrace();  
        } catch (IOException ioexc) {  
            System.err.println(">> fatal transport error: " + ioexc.getMessage());  
            ioexc.printStackTrace();  
        }  
    }

    private void getPushDetail(int hot, int stars, int page)
    {
        // 推文首頁.
        // GetMethod get = new GetMethod("/push/index.php?hot=" + hot + "&stars=" + stars + "&page=" + page);

        // 好友推文.
        GetMethod get = new GetMethod("/push/index.php?friend=post&star=&page=" + page);

        // 傳送已登入驗證
        get.setRequestHeader("not_rem_login", "1");
        get.setRequestHeader("expires", "Sun, 28-Dec-2012 13:21:53 GMT");
        get.setRequestHeader("path", "/");
        get.setRequestHeader("domain", "funp.com");
        try {
            client.executeMethod(get);
            String response = readSource(get).toLowerCase().replaceAll("\r", "").replaceAll("\n", "").replaceAll("\t", "");
            String searchDescriptionReg = "<div.class=['\"]description['\"].id=['\"]description_([0-9]+)['\"]>";
            Pattern pattern = Pattern.compile(searchDescriptionReg); // use regular expression to parse image src.
            Matcher matcher = pattern.matcher(response);
            while (matcher.find()) {
                try {
                    String id = matcher.group(1);
                    String description = matcher.group(2);

                    // 推文.
                    pusher.push(client, id, hot, stars, page);
                    // 加入最愛.
                    addfavo.add(client, id, hot, stars, page);

                    // 顯示進度
                    System.out.println("> id: " + id + ", description: " + description);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (HttpException httpexc) {
            System.err.println(">> Fatal protocol violation: " + httpexc.getMessage());
            httpexc.printStackTrace();
        } catch (IOException ioexc) {
            System.err.println(">> Fatal transport error: " + ioexc.getMessage());
            ioexc.printStackTrace();
        } finally {
            get.releaseConnection();
        }
    }

    // read response html source.
    private String readSource(HttpMethodBase method) throws IOException
    {
        BufferedInputStream inputStream = new BufferedInputStream(method.getResponseBodyAsStream());
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
        char[] buff = new char[1024];
        StringBuilder htmlSource = new StringBuilder();
        int c = 0;
        while ((c = bufferedReader.read(buff, 0, 1024)) != -1) {
            htmlSource.append(buff, 0, c);
        }
        // deal with the response.
        String response = htmlSource.toString();

        return response;
    }

    public static void main(String[] args) throws Exception
    {
        new FunpAutoLogin().login();
    }
}

FunpPusher:

package org.sam.funp;

import java.io.IOException;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;

public class FunpPusher
{
    public FunpPusher()
    {
        System.out.println("> pusher created.");
    }

    public void push(HttpClient client, String no, int hot, int star, int page)
    {
        PostMethod method = new PostMethod("/push/push.ajax.php");

        method.setRequestHeader("not_rem_login", "1");
        method.setRequestHeader("expires", "Sun, 28-Dec-2012 13:21:53 GMT");
        method.setRequestHeader("path", "/");
        method.setRequestHeader("domain", "funp.com");

        method.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.4.154.25 Safari/525.19");
        method.setRequestHeader("Referer", "http://funp.com/push/index.php?hot=" + hot + "&stars=" + star + "&page=" + page);
        method.setRequestHeader("X-Prototype-Version", "1.5.1.1");
        method.setRequestHeader("X-Requested-With", "XMLHttpRequest");
        method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        method.setRequestHeader("Accept", "text/javascript, text/html, application/xml, text/xml, */*");
        method.setRequestHeader("Accept-Encoding", "gzip,deflate,bzip2,sdch");
        method.setRequestHeader("X-SDCH", "Chrome 0.4.154.25");
        method.setRequestHeader("Accept-Language", "zh-TW,zh,en-US,en");
        method.setRequestHeader("Accept-Charset", "Big5,*,utf-8");
        method.setRequestHeader("Host", "funp.com");
        method.setRequestHeader("Content-Length", String.valueOf(7 + no.length()));
        method.setRequestHeader("Connection", "keep-alive");
        method.setRequestHeader("Cache-Control", "max-age=0");

        NameValuePair pid = new NameValuePair("pid", no);
        NameValuePair blank = new NameValuePair("_", "");
        method.setRequestBody(new NameValuePair[] { pid, blank });

        try {
            client.executeMethod(method);

        } catch (HttpException httpexc) {
            System.err.println(">> Fatal protocol violation: " + httpexc.getMessage());
            httpexc.printStackTrace();
        } catch (IOException ioexc) {
            System.err.println(">> Fatal transport error: " + ioexc.getMessage());
            ioexc.printStackTrace();
        } finally {
            method.releaseConnection();
        }
    }
}

FunpAddFavo:

package org.sam.funp;

import java.io.IOException;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.GetMethod;

public class FunpAddFavo
{
    public FunpAddFavo()
    {
        System.out.println("> addfavo created.");
    }

    public void add(HttpClient client, String no, int hot, int star, int page)
    {
        GetMethod method = new GetMethod("/push/doFavoPost.ajax.php?type=add&pid=" + no);

        method.setRequestHeader("not_rem_login", "1");
        method.setRequestHeader("expires", "Sun, 28-Dec-2012 13:21:53 GMT");
        method.setRequestHeader("path", "/");
        method.setRequestHeader("domain", "funp.com");

        method.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.4.154.25 Safari/525.19");
        method.setRequestHeader("Referer", "http://funp.com/push/index.php?hot=" + hot + "&stars=" + star + "&page=" + page);
        method.setRequestHeader("X-Prototype-Version", "1.5.1.1");
        method.setRequestHeader("X-Requested-With", "XMLHttpRequest");
        method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        method.setRequestHeader("Accept", "text/javascript, text/html, application/xml, text/xml, */*");
        method.setRequestHeader("Accept-Encoding", "gzip,deflate,bzip2,sdch");
        method.setRequestHeader("X-SDCH", "Chrome 0.4.154.25");
        method.setRequestHeader("Accept-Language", "zh-TW,zh,en-US,en");
        method.setRequestHeader("Accept-Charset", "Big5,*,utf-8");
        method.setRequestHeader("Host", "funp.com");
        method.setRequestHeader("Connection", "keep-alive");

        try {
            client.executeMethod(method);
        } catch (HttpException httpexc) {
            System.err.println(">> Fatal protocol violation: " + httpexc.getMessage());
            httpexc.printStackTrace();
        } catch (IOException ioexc) {
            System.err.println(">> Fatal transport error: " + ioexc.getMessage());
            ioexc.printStackTrace();
        } finally {
            method.releaseConnection();
        }
    }
}

Java SQLParser SQL語法解析器

最近案子因為寫了一個輕便型的小型資料庫, 所以要用的SQL指令解析來動作, 基本上這支程式解析簡單的一層SQL指令沒有問題, 多層則是不行使用的。

剛好順便複習一下Regular Expression, 都快不認識它了哈哈!!

Sample Code:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SqlParser
{
    public static String type;
    public static String conditions;
    public static String tables;
    public static String order;
    public static String where;

    public static void parse(String sql)
    {
        String regex = "";

        type = "";
        conditions = "";
        tables = "";
        order = "";
        where = "";

        if (sql.indexOf("select") == 0) {

            type = "select";
            if (isContains(sql, "\\s+where\\s+")) {
                regex = "(select)(.+)(from)(.+)(where*)";
            } else if (isContains(sql, "\\s+order\\s+")) {
                regex = "(select)(.+)(from)(.+)(order*)";
            } else {
                regex = "(select)(.+)(from)(.+)($)";
            }
            tables = getMatchedPosition(regex, sql, 4);
            conditions = getMatchedPosition(regex, sql, 2);
            order = getOrderIndex(sql);
            where = getWhere(sql);

        } else if (sql.indexOf("insert") == 0) {

            type = "insert";
            regex = "(insert.*into)(.+)(values)(.+)";
            tables = getMatchedPosition(regex, sql, 2);
            conditions = getMatchedPosition(regex, sql, 4);

        } else if (sql.indexOf("update") == 0) {

            type = "update";
            regex = "(update)(.+)(set)(.+)(where*)";
            tables = getMatchedPosition(regex, sql, 2);
            conditions = getMatchedPosition(regex, sql, 4);
            where = getWhere(sql);

        } else if (sql.indexOf("delete") == 0) {

            type = "delete";
            if (isContains(sql, "\\s+where\\s+")) {
                regex = "(delete.*from)(.+)(where)(.+)";
                where = getMatchedPosition(regex, sql, 4);
            } else {
                regex = "(delete.*from)(.+)($)";
            }
            tables = getMatchedPosition(regex, sql, 2);
        }
    }

    // get matched string from position.
    private static String getMatchedPosition(String regex, String text, int position)
    {
        try {
            // ignore string case.
            Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
            Matcher matcher = pattern.matcher(text);
            while (matcher.find()) {
                return matcher.group(position).trim();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // get where condition.
    private static String getWhere(String sql)
    {
        String regex = "";

        if (isContains(sql, "where\\s+")) {
            if (isContains(sql, "order\\s+by")) {
                regex = "(where\\s+)(.+)(order)";
            } else {
                regex = "(where\\s+)(.+)($)";
            }
        } else {
            return null;
        }

        return getMatchedPosition(regex, sql, 2).trim();
    }

    // get order by content.
    private static String getOrderIndex(String sql)
    {
        String regex = "";

        if (isContains(sql, "order\\s+by")) {
            regex = "(order\\s+by)(.+)($)";
        } else {
            return null;
        }

        return getMatchedPosition(regex, sql, 2).trim();
    }

    // check if contains.
    private static boolean isContains(String lineText, String word)
    {
        // ignore string case.
        Pattern pattern = Pattern.compile(word, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(lineText);
        return matcher.find();
    }

    public static void main(String[] args)
    {
        SqlParser.parse("select * from ADMIN_MSTR order by ID desc");
        System.out.println("tables: " + tables + ", condition: " + conditions);

        SqlParser.parse("select AM_NO, AM_PASSWD from ADMIN_MSTR where ID=3");
        System.out.println("tables: " + tables + ", condition: " + conditions + ", where: " + where);

        SqlParser.parse("select AM_NO, AM_PASSWD from ADMIN_MSTR where ID>0 order by ID desc");
        System.out.println("tables: " + tables + ", condition: " + conditions + ", where: " + where + ", order by: " + order);

        SqlParser.parse("insert into ADMIN_MSTR(ID, NAME, PASSWD) values ('1', 'Sam', 'abcd1234')");
        System.out.println("tables: " + tables + ", condition: " + conditions);

        SqlParser.parse("update ADMIN_MSTR set NAME='ABCD', PASSWD='123465' where ID=1");
        System.out.println("tables: " + tables + ", condition: " + conditions + ", where: " + where);

        SqlParser.parse("delete from ADMIN_MSTR where ID=2");
        System.out.println("tables: " + tables + ", where: " + where);
    }
}

2008年11月27日 星期四

信用卡校驗Java版


看了http://demo.tc/view.aspx?id=398這篇文章有感而發寫出Java版本, 以後應該有機會用到。

信用卡校驗規則為:

1.必須是十六個數字.
2.由左至右取出奇數位數字乘2, 如果大於9則減去9.
3.合併奇數位與偶數位每一個數字總合必須能夠被10整除.
4.開頭第一個字元為4則是Visa, 頭兩個字元大於50則為Master.

注意一下Java char轉int 不可以用(int)char強制轉, 否則會變成ascii code, 請使用Character的digit(char ch, int radix).我們要轉十進位數字所以radix填10.

The Character class wraps a value of the primitive type char in an object.
digit(char ch, int radix) Returns the numeric value of the character ch in the specified radix.

Sample Code:

public static String checkCreditCard(String cardNo)
{
    String result = "Wrong card number!!";
    try {
        // length validation must bigger than sixteen.
        int length = cardNo.length();
        if (length == 16) {

            String type = "";
            if (cardNo.substring(0, 2).compareTo("50") >= 0) {
                type = "Master";
            } else if (cardNo.substring(0, 1).equals("4") == true) {
                type = "Visa";
            }

            // separate the card number.
            char[] sepCard = cardNo.toCharArray();
            // create a new array.
            int[] newCard = new int[16];

            for (int i = 0; i < sepCard.length; i++) {
                if ((i + 1) % 2 == 0) {
                    newCard[i] = Character.digit(sepCard[i], 10);
                } else {
                    newCard[i] = Character.digit(sepCard[i], 10) * 2;
                    if (newCard[i] > 9)
                        newCard[i] -= 9;
                }
            }

            int sum = 0;
            for (int i = 0; i < newCard.length; i++) {
                sum += newCard[i];
            }

            if (sum % 10 == 0)
                result = type + " card.";
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return result;
}

Java自製元件 -- HexTransfer


分享一下我平常在使用的Java自製元件, 這個元件功能為:

1. 轉換byte成16進制HexCode String與HexCode String還原成byte。
2. 整數與byte陣列互轉(用作Socket位元傳輸很方便)。

調用方法為: HexTransfer.byteToHexString(...);

HexTransfer:

public class HexTransfer
{

    // encode byte to hex code string.
    public static String byteToHexString(byte b)
    {
        String str = "";
        if ((b & 0xff) < 16) {
            str = "0";
        }
        str = str + Integer.toHexString(b & 0xff);
        return str;
    }

    // encode normal string to hex string.
    public static String stringToHexString(String str)
    {
        String hexString = "";
        for (int i = 0; i < str.length(); i++) {
            int ch = (int) str.charAt(i);
            String strHex = Integer.toHexString(ch);
            hexString = hexString + strHex;
        }
        return hexString;
    }

    // decode hex string to normal string.
    public static String hexToString(String str)
    {
        byte[] baKeyword = new byte[str.length() / 2];
        for (int i = 0; i < baKeyword.length; i++) {
            try {
                baKeyword[i] = (byte) (0xff & Integer.parseInt(str.substring(i * 2, i * 2 + 2), 16));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        try {
            str = new String(baKeyword, "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return str;
    }

    // decode hex string to byte.
    public static byte hexToByte(String str)
    {
        return (byte) Integer.parseInt(str, 16);
    }

    // transfer integer to byte array.
    public static byte[] integerToBytes(int intValue)
    {
        byte[] result = new byte[4];
        result[0] = (byte) ((intValue & 0xFF000000) >> 24);
        result[1] = (byte) ((intValue & 0x00FF0000) >> 16);
        result[2] = (byte) ((intValue & 0x0000FF00) >> 8);
        result[3] = (byte) ((intValue & 0x000000FF));
        return result;
    }

    // byte array to integer.
    public static int bytesToInteger(byte[] byteVal)
    {
        int result = 0;
        for (int i = 0; i < byteVal.length; i++) {
            int tmpVal = (byteVal[i] << (8 * (3 - i)));
            switch (i) {
                case 0:
                    tmpVal = tmpVal & 0xFF000000;
                    break;
                case 1:
                    tmpVal = tmpVal & 0x00FF0000;
                    break;
                case 2:
                    tmpVal = tmpVal & 0x0000FF00;
                    break;
                case 3:
                    tmpVal = tmpVal & 0x000000FF;
                    break;
            }
            result = result | tmpVal;
        }
        return result;
    }
}

2008年11月26日 星期三

TestDriven with JUnit


一般程式開發人員的習慣是撰寫完一段功能後再加以測試, 也就是說先由開發者角度去創建系統。但測試驅動開發(Test-Driven-Development)卻是從使用的角度開始, 反向讓開發人員回頭撰寫正確執行的程式。

----------------- 何謂測試驅動開發TDD -----------------

你可能會覺得連內容都沒有要怎麼測試呢?沒錯, 測試驅動的用意即是如此!
例如我希望有一個功能是將輸入的兩個數字相加。那測試端就必須事先模擬ex: a = 1, b = 2 。期望值為兩數相加將會等於3, 假設我的方法沒有將處理結果等於3即為錯誤。
一開始主程式的方法只是一個框架:

public int add(int a, int b) {  
     // 尚無任何內容  
     return 0;  
}

測試框架則是已經寫完使用情形:

public int testAdd(){  
     int a = 1;  
     int b = 2;  
     int expect = a + b;     // 期望值  
     if(expect == add(a, b)){  
          System.out.println("Testing success");  
     }else{  
          System.out.println("Testing failed");  
     }  
}

到目前為止測試框架的結果絕對都是Testing failed, 因為主程式add還沒有計算的過程。
現在讓我們回過頭來編寫 add 方法:

public int add(int a, int b){  
     return a + b;  
}

再去測試一次得到Testing success, 大功告成!!
所以測試驅動開發能讓開發者模擬任何情況, 例如最常見的輸入null值等等作相對應的處理, 並且強迫設計程式時,去考慮到物件的低耦合。
上述的範例只是告知各位測試驅動的原理, 事實上 JUnit 並不需要把測試環境寫在同一個類別中, 而是獨立出來的, 所以開發者可以放心使用, 不必擔心程式碼到後期很難管理。

----------------- JUnit簡介 -----------------

JUnit是一個Java語言的單元測試框架。它由Kent Beck和Erich Gamma建立並包括了以下的特性:

1. 對預期結果作斷言
2. 提供測試裝備的生成與銷毀
3. 易於組織與執行測試
4. 圖型與文字介面的測試器

來自JUnit的體驗對測試驅動開發是很重要的,所以一些 JUnit知識經常 和測試驅動開發的討論融合在一起。可以參考Kent Beck的 《Test-Driven Development: By Example》一書(有中文版)。

JUnit 的測試主要由 TestCase、TestSuit 與 TestRunner 三部份架構起來。

詳細請參閱良葛格學習筆記:
TestSuite: http://caterpillar.onlyfun.net/Gossip/JUnit/TestSuite.htm
TestCase: http://caterpillar.onlyfun.net/Gossip/JUnit/TestCase.htm
Failure、Error: http://caterpillar.onlyfun.net/Gossip/JUnit/FailureError.htm

----------------- 相關教學 -----------------

以下網址收錄了詳細的影音教學, 而Eclipse 3.1以後即預設支援JUnit test case, 所以使用Eclipse的朋友們可以事半功倍的來實現測試框架。

JUnit 官網: http://www.junit.org/
影片教學(with Eclipse & NetBeans): http://www.blogjava.net/beansoft/archive/2007/10/29/156650.html
文字教學: http://caterpillar.onlyfun.net/Gossip/JUnit/JUnitGossip.htm

Java PDFParser


之前寫文件搜尋時候使用的PDF擷取器, 功能為將PDF內容抽出存成文字檔, 發布出來給大家參考。

首先要下載所需套件pdfBox: http://www.pdfbox.org/, 下載後將src/PDFBox-0.7.3.jarexternal/FontBox-0.1.0-dev.jar置放到classpath中。

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import org.pdfbox.pdmodel.PDDocument;
import org.pdfbox.util.PDFTextStripper;

public class PDFParser
{
    public void readPdf(String file) throws Exception
    {
        boolean sort = false;             // is sort.
        String pdfFile = file;            // pdf file name.
        String textFile = null;           // output file.
        String encoding = "UTF-8";        // encode type.
        int startPage = 1;                // parse start page no.
        int endPage = Integer.MAX_VALUE;  // parse end page no.
        Writer output = null;             // output writer.
        PDDocument document = null;       // PDF Document.
        try {
            URL url = new URL(pdfFile);   // create connection from source file.
            document = PDDocument.load(pdfFile);  // use PDDocument to load file.
            String fileName = url.getFile();  // get pdf's name.
            
            if (fileName.length() > 4) {  // name output file.
                File outputFile = new File(fileName.substring(0, fileName.length() - 4) + ".txt");
                textFile = outputFile.getName();
            }
            
            // create connection to output file.
            output = new OutputStreamWriter(new FileOutputStream(textFile), encoding);
            
            // use PDFTextStripper to get content.
            PDFTextStripper stripper = new PDFTextStripper();
            stripper.setSortByPosition(sort); // set sort.
            stripper.setStartPage(startPage); // set start page.
            stripper.setEndPage(endPage);     // set end page.
            
            // write to output file.
            stripper.writeText(document, output);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } finally {
            // close all stream.
            if (output != null) {
                output.close();
            }
            if (document != null) {
                document.close();
            }
        }
    }

    public static void main(String[] args)
    {
        PDFParser pdfReader = new PDFParser();
        try {
            // read pdf content.
            pdfReader.readPdf("place your pdf location here");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

flashplayer 9.0.124.0 跨網域無法存取 XMLSocket 解決

這篇文章之前已經發佈過一次(我還沒被砍掉的一百多篇中...), 現在把它補回來!
flashplayer 9.0.124.0 由於SandBox存取規則變更, 所以會先從843port開始找, 如找不到在依actionscript中指定路徑或根目錄底下尋找crossdomain.xml。
但不知為何9.0.124.0版本如果沒有置放843port服務的話, 自己並不會去尋找crossdomain.xml, 這就等於逼著大家放一個驗證policy的server在伺服器上。
這裡給大家一個java寫的範本參考, 也是我目前在線上運作的版本。

Google News Parser

我們都知道Google新聞裡彙整了該地區所有媒體的新聞,所以不必再一家一家查訪, 直接去Google擷取就可以。
進入Google新聞頁面後點選類別, 在左下方有一塊 RSS | ATOM 資訊提供的連結, 點選RSS並複製出網址, 這個XML內容就是我們要分析的地方。
內容都包覆在item節點中, 詳細內容請各位自己判別, 這裡不多加贅述。
Sample Code:
import java.io.*;
import java.net.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class GoogleNewsParser
{

    public GoogleNewsParser()
    {
        try {
            // create factory.
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

            // using factory to create xml parser.
            DocumentBuilder db = dbf.newDocumentBuilder();

            // create connection.
            String url = "http://news.google.com/news?ned=tw&topic=b&output=rss";
            URLConnection conn = new URL(url).openConnection();

            // Google always needs an identification of the client.
            conn.setRequestProperty("User-Agent", "RSSFeed");
            InputStream in = conn.getInputStream();

            // start parsing.
            Document doc = db.parse(in);
            NodeList nl = doc.getElementsByTagName("item");

            // parent node.
            StringBuffer data = new StringBuffer();
            data.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
            data.append("\r\n");
            data.append("<RSS>");
            data.append("\r\n");

            for (int i = 0; i < nl.getLength(); i++) {

                // child nodes.
                NodeList node = nl.item(i).getChildNodes();
                data.append("\t<news id='" + i + "'>");
                data.append("\r\n");
                for (int j = 0; j < node.getLength(); j++) {
                    data.append("\t\t<" + node.item(j).getNodeName() + ">");
                    if (node.item(j).getNodeName().equals("description")) {
                        if (node.item(j).getFirstChild().getNodeValue().indexOf("img src=") >= 0)
                            data.append(node.item(j).getFirstChild().getNodeValue().split("img src=")[1].split("&")[0]);
                    } else {
                        data.append(node.item(j).getFirstChild().getNodeValue());
                    }
                    data.append("</" + node.item(j).getNodeName() + ">");
                    data.append("\r\n");
                }
                data.append("\t</news>");
                data.append("\r\n");
            }
            data.append("</RSS>");
            data.append("\r\n");

            System.out.println(data.toString());

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        System.out.println("Parsing news, please wait...");
        new GoogleNewsParser();
    }
}

運用Apache HttpClient實作Get與Post動作

HttpClient 簡介:

HTTP 協定是現在 Internet 上使用得最多、最重要的協定,越來越多的 Java 應用程序需要直接通過 HTTP 協定來訪問網路資源。 雖然在 JDK 的 java.net 包中已經提供了訪問 HTTP 協定的基本功能,但是對於大部分應用程序來說,JDK 類別庫本身提供的功能還不夠豐富和靈活。 HttpClient 是 Apache Jakarta Common 下的子項目,用來提供高效能、最新、功能豐富的支持 HTTP 協定的client端開發工具,並且它支持 HTTP 協定最新的版本和建議。 HttpClient 已經應用在很多的項目中,比如 Apache Jakarta 上很著名的另外兩個開源項目 Cactus 和 HTMLUnit 都使用了 HttpClient,更多使用 HttpClient 的應用可以參見http://wiki.apache.org/jakarta-httpclient/HttpClientPowered。 HttpClient 項目非常活躍,使用的人還是非常多的。目前 HttpClient 正式版本是 3.1。

HttpClient 功能介紹

以下列出的是 HttpClient 提供的主要的功能,要知道更多詳細的功能可以參見 HttpClient 的主頁。

1. 實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)

2. 支持自動轉向

3. 支持 HTTPS 協議

4. 支持代理服務器

下面將逐一介紹怎樣使用這些功能。首先,我們必須安裝好 HttpClient。

HttpClient 可以在http://hc.apache.org/downloads.cgi下載

HttpClient用到了logging,你可以從這個地址http://commons.apache.org/downloads/download_logging.cgi下載到 common-logging,從下載後的壓縮包中取出 commons-logging.jar 加到 CLASSPATH 中

HttpClient用到了codec,你可以從這個地址http://commons.apache.org/downloads/download_codec.cgi 下載到最新的 common-codec,從下載後的壓縮包中取出 commons-codec-1.x.jar 加到 CLASSPATH 中

Get Method:


import java.io.IOException;

import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.HttpMethodParams;

public class HttpGet
{
    private static String url = "http://www.apache.org/"; // 目標網址.

    public static void main(String[] args)
    {
        // 建立HttpClient實體.
        HttpClient client = new HttpClient();

        // 建立GetMethod實體, 並指派網址, GetMethod會自動處理該網轉址動作, 如果不想自動轉址請呼叫 setFollowRedirects(false).
        GetMethod method = new GetMethod(url);

        // 這段代碼用意為連接不到時自動重新��試三次.
        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false));

        try {
            // 返回狀態值.
            int statusCode = client.executeMethod(method);
            if (statusCode != HttpStatus.SC_OK) {
                System.err.println("Method failed: " + method.getStatusLine());
            }

            // 取得回傳資訊.
            byte[] responseBody = method.getResponseBody();
            System.out.println(new String(responseBody));

        } catch (HttpException httpexc) {
            System.err.println("Fatal protocol violation: " + httpexc.getMessage());
            httpexc.printStackTrace();
        } catch (IOException ioexc) {
            System.err.println("Fatal transport error: " + ioexc.getMessage());
            ioexc.printStackTrace();
        } finally {

            // ** 無論如何都必須釋放連接.
            method.releaseConnection();
        }
    }
}

由於是執行在網路上的程序,運行executeMethod方法時,需要處理兩個異常,分別是HttpException和IOException。 第一種異常的原因主要可能是在建立GetMethod的時候輸入網址錯誤,比如不小心將"http"寫成"htp",或者遠端返回的資訊內容不正常等,並且該異常發生是不可恢復的。

第二種異常一般是由於網路原因引起的異常,對於這種異常 (IOException),HttpClient會根據你指定的恢復策略自動試著重新執行executeMethod方法。

HttpClient的恢復策略可以自定義(通過實現接口HttpMethodRetryHandler來實現)。通過httpClient的方法setParameter設置你實現的恢復策略,這裡使用的是系統提供的預設恢復方式,該方式在碰到第二類異常的時候將自動重試3次。

executeMethod返回值是一個整數,表示了執行該方法後服務器返回的狀態碼,該狀態碼能表示出該方法執行是否成功、需要認證或者頁面轉址(預設狀態下GetMethod是自動處理轉址)等。

POST Method:


import java.io.IOException;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;

public class HttpPost
{
    private static String url = "http://www.apache.org/"; // 目標網址.

    public static void main(String[] args)
    {
        // 建立HttpClient實體.
        HttpClient client = new HttpClient();

        // 建立PostMethod實體, 並指派網址
        PostMethod post = new PostMethod(url);

        // 建立NameValuePair陣列�儲欲傳送的資料, 對照為 (名稱, 內容)
        NameValuePair[] data = { new NameValuePair("Name", "Sam Wang"), new NameValuePair("Passwd", "Test1234") };

        // 將NameValuePair陣列設置到請求內容中
        post.setRequestBody(data);

        try {
            // 返回狀態值.
            int statusCode = client.executeMethod(post);
            if (statusCode != HttpStatus.SC_OK) {
                System.err.println("Method failed: " + post.getStatusLine());
            }

            // 取得回傳資訊.
            byte[] responseBody = post.getResponseBody();
            System.out.println(new String(responseBody));

        } catch (HttpException httpexc) {
            System.err.println("Fatal protocol violation: " + httpexc.getMessage());
            httpexc.printStackTrace();
        } catch (IOException ioexc) {
            System.err.println("Fatal transport error: " + ioexc.getMessage());
            ioexc.printStackTrace();
        } finally {

            // ** 無論如何都必須釋放連接.
            post.releaseConnection();
        }
    }
}

PostMethod運作重點大致上和GetMethod相同, 唯一不同的是傳送變數必須用NameValuePair[]陣列儲存, 再設置到requestBody中。