Java之父James Gosling在他的部落格貼出一幅畫,應該非常貼切許多Java人(或者是傾向Open Source程式開發者)的心聲...

如果你曾經使用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(); } }
測試了一個禮拜應該是沒有問題了, 發佈出來讓大家參考, 使用此程式先要有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(); } } }
剛好順便複習一下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); } }
信用卡校驗規則為:
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; }
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; } }
----------------- 何謂測試驅動開發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
首先要下載所需套件pdfBox: http://www.pdfbox.org/, 下載後將src/PDFBox-0.7.3.jar與external/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(); } } }
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(); } }
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中。