﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-emu in blogjava-文章分类-java技术</title><link>http://www.blogjava.net/emu/category/1379.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 11:31:48 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 11:31:48 GMT</pubDate><ttl>60</ttl><item><title>[转] 讓錯的程式看得出錯 </title><link>http://www.blogjava.net/emu/articles/63112.html</link><dc:creator>emu</dc:creator><author>emu</author><pubDate>Fri, 11 Aug 2006 19:18:00 GMT</pubDate><guid>http://www.blogjava.net/emu/articles/63112.html</guid><wfw:comment>http://www.blogjava.net/emu/comments/63112.html</wfw:comment><comments>http://www.blogjava.net/emu/articles/63112.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/emu/comments/commentRss/63112.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/emu/services/trackbacks/63112.html</trackback:ping><description><![CDATA[
		<table class="slugTable" cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<p class="slugText">
												<strong>
														<a href="http://chinesetrad.joelonsoftware.com/Articles/Wrong.html" target="_blank">讓錯的程式看得出錯</a>
												</strong>
										</p>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<font color="#808080">
		</font>
		<font color="#808080">
				<font size="2">作者: 周思博 (Joel Spolsky)<br />譯： Paul May 梅普華 <br />2005.5.11<br /></font>
				<br />
		</font>
		<!--StartFragment --> 時間回到1983年九月, 我第一個真正的工作是在以色列的Oranim. 這家大型麵包工廠每晚都用六個貨機般大的巨型爐子烤出為數十萬的麵包. 
<p>我第一次走進那家麵包廠時覺得裡頭實在髒得離譜. 爐壁發黃機器生鏽而且到處都是油.</p><p>"這裡一直都這麼髒嗎?"我問道.</p><p>"什麼? 你講這什麼話?"經理回答說."我們才剛打掃過. 這已經是幾週以來最乾淨的時候了."</p><p>說得真好!</p><p>我花了好幾個月每天早上打掃才真正瞭解他們的意思. 對麵包工廠來說, 乾淨是指機器裡沒有生麵糰在烤, 垃圾堆裡沒有發酵的麵糰, 而且地板上也沒有堆生麵糰.</p><p>乾淨並不是指爐子漆得雪白亮麗. 爐子大概十年才會漆一次, 並不會每天都來一回. 乾淨也不是說把油擦得乾乾淨淨. 事實上很多機器都得定期上油, 一層薄淨的油通常暗示機器剛做過清潔保養.</p><p><img style="MARGIN-LEFT: 5px" alt="This is what a dough rounder looks like." src="http://chinesetrad.joelonsoftware.com/Images/DoughRounder.PNG" align="right" border="0" /> 麵包工廠裡這整套乾淨的概念都得經由學習而來. 圈外人不可能走進去就能說出哪裡乾淨哪裡髒. 圈外人絕不會想到要看麵糰滾圓機(把方麵糰滾成球形的機器, 見右邊附圖)內壁有沒有刮乾淨. 圈外人會覺舊爐子外壁鑲板掉色是有問題的,因為鑲板很<em>大</em>很顯眼. 不過麵包師傅根本不在意爐子的塗漆開始發黃. 因為麵包的味道還是一樣棒.</p><p>在麵包工廠待兩個月, 你學會如何"看出"乾淨.</p><p>程式碼也是一樣的.</p><p>當你剛開始寫程式或嘗試讀用新語言寫的程式時, 所有程式碼看起來都一樣神秘不可解. 而在瞭解該種程式語言前, 你連明顯的語法錯誤都看不出來.</p><p>在學習的第一階段, 你會開始發現一種我們通常稱為"編程風格"的東西. 於是你開始注意那些不遵循縮排標準的程式碼和有用多個大寫字母的變數.</p><p>也就是這個階段你會說:"該死的混蛋, 我們這裡<em>一定</em>要定出一些一致的編程風格!" 然後第二天寫出一份你們團隊用的編程風格, 接下來用六天來討論One True Brace Style(譯著:就是K&amp;R style), 然後再花三星期把舊程式碼改寫成符合One True Brace Style, 一直做到經理發現並責怪你把時間浪費在不能賺錢的事為止. 你想想其實不需要一次全部改好, 看到哪裡改到哪裡也沒什麼關係. 於是有一半的程式碼已經改成True Brace Style, 而沒多久你就忘記這件事了. 接下來你就開始滿腦子想著其他與賺錢無關的事, 比如把某個字串類別換成另一個字串類別等等.</p><p>當你對某特定環境下的程式愈來愈精通時, 就會開始學著看到其他東西. 那些東西可能完全合法並符合編程風格, 卻又會讓你擔心不已.</p><p>舉例來說在C語言裡:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>char* dest, src;</strong></tt></p></blockquote><p>這是合語法的程式碼; 這可能符合你的編程規範, 甚至可能是故意這樣寫的, 不過如果你寫C的經驗夠, 就會注意這種寫法把<tt><strong>dest</strong></tt>宣告成<tt><strong>字元</strong></tt><i>指標</i>卻把<tt><strong>src</strong></tt>宣告成<tt><strong>字元</strong></tt>而已, 這<em>可能</em>是你的意思, 不過也可能不是. 反正這段程式看起來有點不對勁.</p><p>來看更細微的例子:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>if (i != 0)<br />    foo(i);</strong></tt></p></blockquote><p>這段程式是百分之百正確的;它符合大多數的編程規範也完全沒有錯誤, 不過你可能會質疑<tt>if</tt>敘述所接的單敘述主體並未用大括號包起來, 因為你腦子裡想到有人可能會插入另一行程式碼</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>if (i != 0)<br /><span style="BACKGROUND-COLOR: yellow">    bar(i);</span><br />    foo(i);</strong></tt></p></blockquote><p>...又忘記加上大括號, 結果讓<tt><strong>foo(i)</strong></tt>變成永遠會執行! 所以當你看到沒有用大括弧包起來的程式碼區段時, 可能就會感覺到一絲絲讓你不舒服的氣味.</p><p>好啦, 到目前為止我已經提到三種程式師的成就層級:</p><p>1. 你不知道乾淨和髒有什麼分別.</p><p>2. 你對乾淨有粗淺的認知, 主要以是否符合編程規範為準.</p><p>3. 你開始能嗅出藏在表面下不對勁的蛛絲馬跡. 你會察覺這是問題並且找出來修正.</p><p>不過其實還有更高的層次, 而這也就是我真正要說的:</p><p>4. 你有計劃地架構程式碼, 藉助能察覺問題的靈眼讓程式碼更正確.</p><p>這是真正的藝術: 仔細地<em>設計</em>讓錯誤顯而易見的<em>編程規範</em>, 藉此製作出穩固的程式.</p><p>所以現在我要帶你看一個小例子然後再展示一個通用的規則. 你可以利用這個通則設計出創造增加程式穩固的編程規範. 最後我會把主題導引到為某種匈牙利命名法(可能不是讓人們暈到的那種)進行辯護, 並且批判某些環境(也可能不是你最常用的那種環境)下的例外處理.</p><p>不過如果你深信匈牙利命名法不是好東西, 認為例外處理是從自巧克力奶昔以來最棒的發明, 而且完全不想聽聽其他意見, 沒問題, 你可以改去羅力那裡看看<a href="http://neopoleon.com/blog/posts/13932.aspx">好看的漫畫</a>; 反正你在這裡也沒什麼好看的; 事實上在一分鐘內我就會拿出實際的程式碼範例, 這些範例很可能會讓你在不爽前就暈睡過去了. 沒錯. 我想我的計畫是把你哄到沈沈入睡, 趁你睡著無法抵抗時把"匈牙利命名法=好, 例外處理=壞"的想法偷偷塞進你腦子裡面.</p><p><font size="5"><strong>一個例子</strong></font></p><p><img style="MARGIN-LEFT: 5px" alt="Somewhere in Umbria" src="http://chinesetrad.joelonsoftware.com/Images/Umbria.JPG" align="right" border="0" /></p><p>好了. 提到這個例子. 讓我們假裝你正在寫某種web應用程式, 因為這陣子小朋友似乎都流行寫這玩意.</p><p>現在有一種叫跨站腳本漏洞(Cross Site Scripting Vulnearability)的安全漏洞, 縮寫為<a href="http://www.cert.org/advisories/CA-2000-02.html">XSS</a>. 我在這裡不談細節: 你只需要知道在寫web應用程式時, 一定要小心絕不能把使用者填入表單的任何字串直接傳回來.</p><p>舉例來說, 如果你有一個網頁會讓使用者在編輯框輸入姓名, 傳送後就會跳到另一個寫著"你好啊, 張三!"(假設使用者的名字是張三)的網頁. 很好, 這就是個安全漏洞, 因為使用者可能不輸入"張三"而輸入某種奇怪的HTML及JavaScript, 這些奇怪的JavaScript就可能會做些低級事情, 比如讀出你寫的cookie內容轉送到壞人的壞網站去. 而這些低級事現在看起來就是你搞的鬼.</p><p>讓我們把程式用虛擬碼的方法寫出來. 想像以下的程式</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>s = Request("name")</strong></tt></p></blockquote><p>會由HTML表格讀取使用者輸入(一個POST的參數). 如果你曾經寫出下面的程式碼:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><strong><tt>Write "你好, " &amp; Request("name")</tt></strong></p></blockquote><p>那你的網站已經有讓XSS攻擊的漏洞了. 光這樣就夠了.</p><p>你必須在複製回HTML之前先編碼才能避免這個漏洞. 所謂編碼就是把<tt>"</tt>換成<tt>&amp;quot;</tt>, 把<tt>&gt;</tt>換成<tt>&amp;gt;</tt>, 如此類推. 所以</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>Write "你好, " &amp; Encode(Request("name"))</strong></tt></p></blockquote><p>是絕對安全的.</p><p>所有來自使用者的字串都是<em>不安全的</em>. 任何不安全的字串都得先編碼後才能輸出.</p><p>讓我們嘗試設計一組編程規範, 確保當你犯這種錯時程式碼<em>看起來</em>就是錯的. 如果程式碼有錯(至少<em>看起來</em>錯), 就很有機會被修改或審視這段程式的人抓到.</p><p><strong><font size="4">可能方案一</font></strong></p><p>方案一是將所有字串立即編碼, 由使用者取得後馬上就進行:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>s = Encode(Request("name"))</strong></tt></p></blockquote><p>所以我們的規範會寫著: 如果你看到沒有被<tt><strong>Encode</strong></tt>包住的<tt><strong>Request</strong></tt>, 程式一定是錯的.</p><p>你開始訓練自己的眼睛找尋落單的<tt><strong>Request</strong></tt>, 因為它們違反規範.</p><p>這是有用的, 因為只要你遵循規範就不會有XSS問題. 不過這並不是最好的架構. 比方說你可能想要把這些使用者字串存到資料庫裡, 這時候儲存以HTML編碼過的字串並不合理, 因為字串有可能會用在HTML網頁以外的場合. 假如是信用卡處理程式要用時編碼過的資料就會產生問題. 大部份web應用程式開發都會依循一個原則: 所有字串在內部都是<em>未</em>編碼的, 要等到送至HTML網頁的<em>前一瞬間</em>才會處理, 因此這可能並不是正確的架構.</p><p>我們真的要能讓字串維持在不安全格式一段時間.</p><p>好吧. 我再試看看.</p><p><strong><font size="4">可能方案二</font></strong></p><p>如果建立一種編程規範, 要求在<em>寫出</em>任何字串時必須加以編碼, 是否可以滿足要求嗎?</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>s = Request("name")<br /></strong></tt><tt><br /><strong>// 很後面:<br /></strong></tt><strong><tt>Write Encode(s)</tt></strong></p></blockquote><p>現在當你看到一個落單沒有<tt><strong>Encode</strong></tt>跟著的<tt><strong>Write</strong></tt>時就知道有有問題了.</p><p>唉, 這也不太好...有時候你的程式裡會有一小段的HTML碼, 這種情況下是<em>不能夠</em>編碼的:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><strong><tt>If mode = "linebreak" Then prefix = "&lt;br&gt;"</tt></strong></p><p><strong><tt>// 很後面:<br /></tt><tt>Write prefix</tt></strong></p></blockquote><p>這照我們的規範來看是錯的, 我們必須要在輸出時加以編碼:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><strong><tt>Write Encode(prefix)</tt></strong></p></blockquote><p>不過現在應該要新增一行的<tt>"<strong>&lt;br&gt;</strong>"</tt>卻被編碼成<tt><strong>&amp;lt;br&amp;gt;</strong></tt>, 結果變成使用者可以看到的字元<tt><strong>&lt; b r &gt;</strong></tt>. 這樣的解法也不對.</p><p>所以說有時候你不能在讀入字串時編碼, 有時候你也不能在輸出時編碼, 這兩種提案都不能用. 可是沒有適當的編碼規範, 我們還是有出下列問題的風險:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>s = Request("name")</strong></tt></p><p><tt>...好幾頁之後...<br /></tt><strong><tt>name = s</tt></strong></p><p><tt>...好幾頁之後...<br /></tt><strong><tt>recordset("name") = name // 把名字存在資料庫中的姓名欄</tt></strong></p><p><tt>...好幾天後...<br /></tt><strong><tt>theName = recordset("name") </tt></strong></p><p><tt>...好幾頁甚至好幾個月之後...<br /></tt><tt><strong>Write theName</strong></tt></p></blockquote><p>我們還會記得要對字串編碼嗎? 你在任何單一的地方都看不到問題. 連可以嗅的地方都沒有. 如果這種程式有一大缸子, 要一大票偵探才能追蹤出所有字串的來源並確認是否已編碼..</p><p><strong><font size="4">正解</font></strong></p><p>所以讓我提議一種能用的編程規範. 我們只有一個規則:</p><p>所有來自使用者的字串都必須存在以"us"(表示Unsafe String,不安全字串)為字首的變數(或資料庫欄位)中. 所有經HTML編碼或來自確認安全來源的字串都必須存在以"s"(表示Safe String,安全字串)為字首的變數中.</p><p>讓我們重寫程式, 只是依規範重新命名變數, 其他完全不動.</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><strong><tt>us = Request("name")</tt></strong></p><p><tt>...好幾頁之後...<br /><strong>usName = us</strong></tt></p><p><tt>...好幾頁之後...<br /><strong>recordset("usName") = usName </strong></tt></p><p><tt>...好幾天後...<br /><strong>sName = Encode(recordset("usName"))</strong></tt></p><p><tt>...好幾頁甚至好幾個月之後...<br /><strong>Write sName</strong></tt></p></blockquote><p>新規範中值得注意的是, 只要遵循編碼規範, 不安全字串相關的錯誤<em>一定可以由單一行的程式碼看出來</em>:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>s = Request("name")</strong></tt></p></blockquote><p>是之前的錯誤, 因為你可以看到<tt>Request</tt>的結果被指派給以<tt>s</tt>開頭的變數, 這違反了規則. <tt>Request</tt>的結果一定是不安全的, 所以必須指派給以"us"開頭的變數.</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><strong><tt>us = Request("name")</tt></strong></p></blockquote><p>一定沒問題.</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>usName = us</strong></tt></p></blockquote><p>一定沒問題.</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>sName = us</strong></tt></p></blockquote><p>一定是錯的.</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>sName = Encode(us)</strong></tt></p></blockquote><p>一定是對的.</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>Write usName</strong></tt></p></blockquote><p>一定是錯的.</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>Write sName</strong></tt></p></blockquote><p>沒問題, 下面也一樣沒問題</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>Write Encode(usName)</strong></tt></p></blockquote><p>每一行程式光是看<em>程式碼</em>本身就足以檢查, 而且如果每一行程式都對, 組合起來整個程式也是對的.</p><p>終於好了, 利用這套編碼規範, 你的眼睛學著看到<tt><strong>Write usXXX</strong></tt>就知道是錯的, 而且你也立即知道要如何修正. 我知道一開始要看到錯誤的程式是有一點難, 不過進行三個星期後你的眼睛就會習慣, 就像麵包廠的工人看到大麵包工廠就會馬上說:"搞什麼鬼, 這裡都沒人在掃哦! 這算啥麵包廠."</p><p>事實上我們可以再把規則延伸一點, 把<tt><strong>Request</strong></tt>和<tt><strong>Encode</strong></tt>函數改名(或封裝)成<tt><strong>UsRequest</strong></tt>和<tt><strong>SEncode</strong></tt>...換句話說, 傳回不安全字串以及安全字串的函數要和變數一樣, 分別要用<tt><strong>Us</strong></tt>及<tt><strong>S</strong></tt>作為字首. 現在看看程式碼:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>us = UsRequest("name")<br /></strong></tt><tt><strong>usName = us<br /></strong></tt><tt><strong>recordset("usName") = usName <br /></strong></tt><strong><tt>sName = SEncode(recordset("usName"))<br /></tt><tt>Write sName</tt></strong></p></blockquote><p>看到我們的成果沒? 現在你可以看看等號兩邊的字首是否相同就能找到錯誤.</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><span></span><strong><u>us</u> = <u>Us</u>Request("name") </strong>// 沒問題, 兩邊都以US開頭<br /><span></span></tt><tt><span></span><strong><u>s</u> = <u>Us</u>Request("name") </strong>// 錯<br /><span></span></tt><tt><span></span><strong><u>us</u>Name = <u>us</u></strong>// 對<br /><span></span></tt><span></span><tt><strong><u>s</u>Name = <u>us</u></strong>// 一定錯.<br /></tt><tt><strong><u>s</u>Name = <u>S</u>Encode(us) </strong>// 一定對.</tt><span></span></p></blockquote><p>我還能再進一步把<tt><strong>Write</strong></tt>改名成<tt><strong>WriteS</strong></tt>並把<tt><strong>SEncode</strong></tt>改名成<strong><tt>SFromUs</tt></strong>:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong><u>us</u> = <u>Us</u>Request("name")<br /></strong></tt><tt><strong><u>us</u>Name = <u>us</u><br /></strong></tt><tt><strong>recordset("<u>us</u>Name") = <u>us</u>Name <br /></strong></tt><strong><tt><u>s</u>Name = <u>S</u>From<u>Us</u>(recordset("<u>us</u>Name"))<br /></tt><tt>Write<u>S</u><u>s</u>Name</tt></strong></p></blockquote><p>這使得錯誤<em>更加</em>顯而易見. 你的眼睛會學習"看出"可疑的程式碼, 另外這也能協助你經由一般撰寫或閱讀程式碼的動作找到隱藏的安全漏洞.</p><p>讓錯的程式看得出錯是很棒沒錯, 不過卻不是所有安全問題的最佳解答. 它無法找到所有可能的問題或錯誤, 因為你可能沒法子看過每一行程式碼. 不過絕對比什麼都不做要好, 而我很希望有套編碼規範能讓錯誤的程式碼至少看起來是錯的. 你馬上就能獲得好處, 每當程式師的眼睛掃過一行程式, 就能檢查並防止某些特定的錯誤.</p><p><font size="5"><strong>一個通則</strong></font></p><p>這種讓錯誤程式看起來錯的作法有個前提, 就是要讓對的東西在螢幕上緊靠在一起. 當我看到某個字串時並要決定 程式碼正確與否, 我必須知道字串出現的所有位置以及字串是安全的還是不安全的. 我不希望這些資料出現在另一個檔案或是要捲動畫面才能看到的另一頁. 我必須能<em>當場</em>看到, 而這說的就是一套變數命名規範.</p><p>有很多其他的例子可以說明, 只要把某些東西搬在一起就可以改善程式碼. 大多數的編程規範都有如下的規則:</p><ul><li>保持函數名稱簡短. 
</li><li>變數宣告的地方離使用的位置愈近愈好. 
</li><li>不要用巨集建立你個人專屬的程式語言. 
</li><li>不要使用<tt>goto</tt>. 
</li><li>不要讓右括弧離左括弧超過一個畫面.</li></ul><p>這些規則有一個共同點, 就是儘量讓一行程式碼實際作用的相關資訊在畫面上愈近愈好. 這樣能提高眼球找出程式實質運作內容的機會.</p><p>大體上我得承認我有點害怕會藏東西的程式語言功能. 當你看到程式碼</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><tt><strong>i = j * 5;</strong></tt></p></blockquote><p>... 就C來說你至少會知道<strong><tt>j</tt></strong>會乘以5而結果會存到<strong><tt>i</tt></strong>. </p><p>不過如果你在C++裡看到相同的片段, 你什麼都不知道. 在C++中唯一能知道真正發生什麼事的方法就是找出<tt><strong>i</strong></tt>和<tt><strong>j</strong></tt>所屬的型別, 而這個型別可能會在完全不一樣的地方宣告. 因為<tt><strong>j</strong></tt>的<tt><strong>運算子*</strong></tt>可能有過荷, 在你要做乘法時會做些很機靈的事. 而<tt><strong>i</strong></tt>的<tt><strong>運算子=</strong></tt>可能也是過荷的, 而兩者型別可能是不相容的, 於是又呼叫到某個自動型別強制轉換的函數. 光是檢查變數的型別還不足以確認, 還得檢查實作該型別的程式碼才行, 萬一實作時又有繼承其他型別就更麻煩了, 因為你得回溯類別繼承的祖宗八代才能找到真正的程式碼, 不巧又有用到別處的多型就<em>真的</em>有大麻煩了, 因為光是知道<tt>i</tt>和<tt>j</tt><em>宣告</em>的型別並不夠, 還得知道它們<em>此刻</em>的型別, 這不知道要看多少的程式碼, 而且依照計算理論的停機問題, 你永遠都不能真的百分之百確定自己已經看完所有地方了(啊啊啊啊啊!!!).</p><p>當你看到C++的<tt><strong>i=j*5</strong></tt>時你只能自求多福了, 兄弟. 這對我來說就降低了光看程式碼找出在問題的能力.</p><p>當然囉, 理論上這應該沒什麼關係. 當你做些過荷<tt>運算子*</tt>之類聰明事時, 只要為了要提供一個優美而安全的抽象罷了. 天啊, 其實<tt>j</tt>是個萬國碼字串型別, 一個萬國碼字串乘以一個整數<em>顯然</em>是把正體中文轉成簡體中文的良好抽象作法, 對嗎?</p><p>問題當然出在沒有絕對安全的抽象方法. 我已經在<a href="file:///C:/Documents%20and%20Settings/spolsky/Local%20Settings/Temporary%20Internet%20Files/OLK2D/LeakyAbstractions.html">抽象出錯定律</a>裡討論很多了, 所以不會在這裡重複.</p><p>Scott Meyers示範了各種抽象出錯(至少是C++)的型式以及所造成的傷害, 他靠這個主題就創出一番事業了. (順便一提, Scott的書<a href="http://www.awprofessional.com/title/0321334876">Effective C++</a>第三版剛剛上市; 整本書都重寫過; 今天就去買一本吧!)</p><p>好吧.</p><p>有點失焦了. 我最好回顧一下到目前為止的內容:</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p>找出能讓錯誤程式看起來錯的編程規範. 讓正確的資訊集中在程式碼中相同的地方, 方便你看出某些問題並立即修正.</p></blockquote><p><font size="5"><strong>我是匈牙利</strong></font></p><p><img style="MARGIN-LEFT: 5px" alt="Lugnano, Umbria, Italy" src="http://chinesetrad.joelonsoftware.com/Images/Lugnano.JPG" align="right" border="0" /> 我們現在回到惡名昭彰的匈牙利命名法.</p><p>匈牙利命名法是微軟程式設計師Charles Simonyi發明的. Simonyi在微軟做的主要計劃是Word; 事實上他還主持了世界上第一個所見即所得的文書處理器(在Xerox Parc名為Bravo計劃). </p><p>在所見即所得的文書處理中會用到可捲動的視窗, 所以座標值有兩種意義:相對於視窗或相對於處理頁. 兩種座標的差異很大, 所以好好安排是非常重要的.</p><p>我猜這正是Simonyi開始採用某些之後被稱作匈牙利命名法的原因之一. 它看起來像匈牙利文, 而Simonyi是從匈牙利來, 所以以匈牙利為名. 在Simonyi版本的匈牙利命名法中, 每個變數都會加一個小寫的字首, 表示變數內容的種類.</p><p><img height="91" alt="打個比方, 如果變數名為rwCol, rw就是字首." src="http://chinesetrad.joelonsoftware.com/Images/hungarian.png" width="130" border="0" /></p><p align="left">我是故意用<em>種類(kind)</em>這個詞, 因為Simonyi在他的文章中誤用了<em>型別(type)</em>, 結果好幾世代的程式師都誤解了他的意思.</p><p>如果你仔細讀Simonyi的文章, 就會發現他所講的和我之前範例所用的命名規範是一樣的, 在我的範例中把<tt><strong>us</strong></tt>和<tt><strong>s</strong></tt>分別定義為不安全字串和安全字串. 這兩者的型別都是<tt><strong>字串</strong></tt>. 如果你把某種字串指派另一種, 編譯器並不會給任何警告, Intellisense也不會說些什麼. 可是他們的語意是不同的; 他們解讀和處理的方式都不同, 要把兩種字串互相指派時還要某些轉換函數做轉換, 否則就會有<em>執行時期</em>的問題. <em>祝</em>你好運.</p><p>微軟內部稱Simonyi對匈牙利命名法的原始概念為應用匈牙利命名法, 因為它用於應用程式部門, 也就是Word及Excel. 在Excel的原始程式碼裡有大量的<tt><strong>rw</strong></tt>和<tt><strong>col</strong></tt>, 你看到這些字首就知道它們指的是行(row)和列(column). 沒錯, 它們都是整數, 可是兩者間的轉換完全沒有意義. 有人告訴我說Word的程式碼裡有大量的<tt><strong>xl</strong></tt>和<tt><strong>xw</strong></tt>, <tt><strong>xl</strong></tt>代表相對於排版頁面的水平座標, 而 <tt><strong>xw</strong></tt>則代表相對視窗的水平座標. 兩者都是整數但卻是不能互轉的. 兩個程式裡都有很多<tt>cb</tt>, 意思是位元組的個數. 沒錯, 這也是整數型別, 不過光看變數名就可以得到更多資訊: 這是位元組的個數, 也就是緩衝區的大小. 另外如果你看到<tt><strong>xl = cb</strong></tt>就可以拉警報了. 這顯然是錯的程式, 雖然<tt><strong>xl</strong></tt>和<tt><strong>cb</strong></tt>都是整數, 可是把以像素為單位的水平位移設成位元組個數絕對是瘋了.</p><p>在應用匈牙利命名法中字首可以用於函數和變數. 因此雖然我真的沒看過Word的原始碼, 我還是敢打賭Word裡一定有個叫<tt><strong>YlFromYw</strong></tt>的函數, 可以把垂直方向的視窗座標轉成垂直方向的排版頁座標. 應用匈牙利命名法用<tt><strong>TypeFromType</strong></tt>取代傳統的 <tt><strong>TypeToType</strong></tt>, 這樣每個函數名就會以傳回的型別開頭, 這正與我稍早在範例中把<strong><tt>Encode</tt>改名為<tt>SFromUs</tt></strong>的作法相同. 事實上在正規的應用匈牙利命名法中Encode函數<em>一定</em>要改名為<tt><strong>SFromUs</strong></tt>. 應用匈牙利命名法在該函數命名上並沒有提供其他選擇. 這其實是件好事, 因為你少一件事要背, 另外也不必擔心<tt>Encode</tt>究竟是用什麼型別. 程式也變得精確多了.</p><p>應用匈牙利命名法非常有用, 特別是當初C語言盛行, 而編譯器尚未提供很有用的型別系統時. </p><p>不過接下來卻出了一些問題.</p><p>黑暗世界占用了匈牙利命名法.</p><p>似乎沒有人知道為什麼或是如何發生的, 不過似乎是視窗團隊中寫文件的人不小心創造出後來名為系統匈牙利命名法的東西.</p><p>某處有人讀了Simonyi的文章看到裡面用了"型別"這個字眼, 因此認為作者指的就是型別, 意思就像是類別或是型別系統中, 或是編譯器所做的型別檢查. 其實不然. 作者很小心並精確的解釋他用"型別"這個字的意義, 不過沒有用. 傷害已經造成了.</p><p>應用匈牙利命名法的字首很有用而且有意義, "ix"表示陣列索引, "c"表示個數, "d"表示兩個數字間的差(比如"dx"表示"寬度"), 如此類推.</p><p>系統匈牙利命名法的字首作用就差多了, "l"表示長整數, "ul"表示正長整數而"dw"代表雙字組(呃, 事實上就是正長整數). 在系統匈牙利命名法中, 字首只能告訴你變數真正的資料型別.</p><p>這誤解了Simonyi的意圖和實作, 差異雖細微實質上卻是完全不同. 這件事唯一的教訓是讓你知道, 如果你寫出些沒人能懂的艱深難解學術文章, 你的想法可能會一再被誤解, 結果變得非常荒謬, 完全違背你的原意. 所以在系統匈牙利命名法中會出現大量的<tt>dwFoo</tt>表示"雙字組的某某", 可惡的是某個變數是雙字組這件事對你幾乎是完全沒用的. 難怪大家都很討厭系統匈牙利命名法.</p><p>系統匈牙利命名法的流傳既深又廣; 它是整個視窗程式設計文件的標準; <a href="http://www.charlespetzold.com/pw5/">Charles Petzold的視窗程式設計</a>(學習視窗程式設計的聖經)等書籍更為它廣為宣揚, 很快的它也成為匈牙利命名法的主要勢力, 即使在微軟內部也一樣. 在微軟內也只有少數不在Word和Excel團隊的程式師瞭解他們搞出什麼樣的錯.</p><p>接下來就是大反抗了. 有群程式師們從一開始就沒搞懂過匈牙利命名法, 他們發現自己用的竟是煩人又幾近無用的分支, 於是就起來反抗. 不過系統匈牙利命名法裡還是有些好東西可以幫你看出問題. 如果用系統匈牙利命名法, 至少會在使用時知道變數型別. 不過沒應用匈牙利命名法那麼有價值就是了.</p><p>大反抗在.NET.第一版發行時到達巔峰, 那時微軟終於告訴大家"不建議使用匈牙利命名法". 這還真是歡聲雷動啊. 我根本不認為微軟會花心思解釋原因. 他們只是掃瞄文件中命名指引的章節然後加上"不要使用匈牙利命名法"的字句. 當時匈牙利命名法非常不受歡迎所以沒有人會真的抱怨, 而除Excel及Word以外的人都因為不必再用這麼麻煩的命名規範而鬆了一口氣, 他們認為在有強型別檢查及Intellisense的時代也不需要這種規範.</p><p>不過應用匈牙利命名法還是很有價值的, 它加強了程式碼的連結讓程式碼更易閱讀, 撰寫, 除錯及維護, 最重要的是它讓錯誤的程式看得出錯. </p><p>在繼續之前還有一件事我說過要做, 就是再罵一次例外處理. 我上次這樣做惹來很多麻煩. 我在周思博趣談軟體首頁上一篇即興的評論中<a href="file:///C:/Documents%20and%20Settings/spolsky/Local%20Settings/Temporary%20Internet%20Files/items/2003/10/13.html">寫</a>說我不喜歡例外處理, 因為它實際上就是隱藏的goto, 我認為這比看得到的goto更糟糕. 當然就有幾百萬人跑出來痛罵我. 全世界唯一跳出來替我辯護的當然也就是Raymond Chen. 順帶一提, 他既然是世界上最好的程式師, 當然得出來講講話, 對嗎?</p><p>這篇文章講到例外處理的重點了. 你的眼睛學著看到錯誤的程式碼, 這樣就能防止問題發生. 為了讓程式能變得真正穩固, 進行程式碼檢視時得有一套能集中資訊的命名規範. 換而言之, 你眼前有關程式運作的資訊愈多, 尋找錯誤的結果愈好. 當你看到以下的程式碼時</p><blockquote dir="ltr" style="MARGIN-RIGHT: 0px"><p><strong><tt>dosomething();<br />cleanup();</tt></strong></p></blockquote><p>...你的眼睛會說沒什麼問題啊. 我們總是要做清除的動作! 不過<tt><strong>dosomething</strong></tt>有可能會引發一個例外, 所以有可能不會呼叫<tt><strong>cleanup</strong></tt>. 用<tt><strong>finally</strong></tt>等很簡單就能修正這個問題, 不過這並不是我的重點: 問題在於要知道<tt><strong>cleanup</strong></tt>一定會被呼叫到的唯一方法, 就是調查整個<tt><strong>dosomething</strong></tt>呼叫樹, 看看是否有任何場合會產生例外. 這也還好, 可控制式例外處理(checked exception)可以讓你不用那麼辛苦, 不過重點是例外處理把資訊分散開來了. 你得去看<em>其他地方</em>才能知道程式能正確執行, 所以無法運用你眼睛天賦的功能去學習看出錯的程式碼, 因為根本沒東西可看.</p><p>如果我寫個小腳本程式, 只是每天一次到處收集資料然後印出來, 這時候例外處理好用得不得了. 我只想忽略所有可能出錯的地方, 直接把整個程式用一個大try/catch包起來, 如果有出什麼問題就用catch把錯誤電郵給自己. 例外處理對簡單隨便寫的程式很有用, 對腳本程式或是不是非常重要或無關生死的程式也不錯. 不過如果你在寫一套作業系統或核電廠程式, 或是用於開心手術的高速電鋸, 例外處理可是危險的很.</p><p>我知道大家會認為我是個無法正確理解例外處理的笨程式師, 完全不知道只有當我衷心接納例外處理後它才能改善我的生活. 這種想法真是太糟糕了. 想要寫出真正可信賴的程式碼, 應該要嘗試用考慮到人有弱點的簡單工具, 而不是靠那些提供有問題的抽象並把副作用隱藏起來, 還認為程式師絕不出錯的複雜工具.</p><p><font size="4"><strong>補充讀物</strong></font></p><p>如果你還是衷心於例外處理, 讀讀Raymond Chen的文章<a href="http://blogs.msdn.com/oldnewthing/archive/2005/01/14/352949.aspx">更乾淨更優雅, 不過更難讀</a>. "<!--StartFragment -->例外處理用得正確與否, 很難由程式碼看得出來... <!--StartFragment -->例外處理太難了, 我實在不夠聰明無法掌握."</p><p>Raymond對致命巨集的文章<a href="http://blogs.msdn.com/oldnewthing/archive/2005/01/06/347666.aspx">A rant against flow control macros</a>討論了另一個讓資訊分散導致程式無法維護的例子. "<!--StartFragment -->當看到使用[巨集]的程式碼時, 你必須看遍各個標頭檔才能瞭解它們的作用."</p><p>想要瞭解匈牙利命名法的歷史背景, 可以由Simonyi的原文<a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvs600/html/hunganotat.asp">匈牙利命名法</a>開始. Doug Klunder在另一篇比較清楚的文章中<a href="http://www.byteshift.de/msg/hungarian-notation-doug-klunder">把它引進Excel團體</a> . 想知道更多匈牙利命名法的故事以及如何被文件撰寫人破壞的始末, 可以去看<a href="http://blogs.msdn.com/larryosterman/archive/2004/06/22/162629.aspx">Larry Osterman</a>站上的貼文, 特別是<a href="http://blogs.msdn.com/larryosterman/archive/2004/06/22/162629.aspx#163721">Scott Ludwig的評論</a>, 或是<a href="http://blogs.msdn.com/rick_schaut/archive/2004/02/14/73108.aspx">Rick Schaut貼的文章</a>.</p><br /><br /><font style="FONT-FAMILY: Georgia, Times, serif" color="#808080" size="2">文件來源: <a href="http://www.joelonsoftware.com/articles/Wrong.html">Making Wrong Code Look Wrong</a> (英文)</font> <img src ="http://www.blogjava.net/emu/aggbug/63112.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/emu/" target="_blank">emu</a> 2006-08-12 03:18 <a href="http://www.blogjava.net/emu/articles/63112.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>StrutsTestCase 试用手记  </title><link>http://www.blogjava.net/emu/articles/4775.html</link><dc:creator>emu</dc:creator><author>emu</author><pubDate>Wed, 18 May 2005 08:19:00 GMT</pubDate><guid>http://www.blogjava.net/emu/articles/4775.html</guid><wfw:comment>http://www.blogjava.net/emu/comments/4775.html</wfw:comment><comments>http://www.blogjava.net/emu/articles/4775.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/emu/comments/commentRss/4775.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/emu/services/trackbacks/4775.html</trackback:ping><description><![CDATA[<TABLE style="BORDER-COLLAPSE: collapse" width="100%" border=0>
<TBODY>
<TR>
<TD class=BlogArticleTitle></TD></TR>
<TR>
<TD class=BlogArticleContent>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 width="100%" border=0>
<TBODY>
<TR>
<TD style="LEFT: 0px; WIDTH: 100%; WORD-WRAP: break-word"><PRE><P><DIV class=post><DIV class=postTitle align=center><A href="http://blog.csdn.net/emu/archive/2005/01/22/263856.aspx"><U><FONT color=#800080>原文连接</FONT></U></A></DIV><DIV class=postTitle align=center>&nbsp;</DIV><DIV class=postTitle>junit不能测struts的action，httpunit也只能测servlet。用struts作项目的时候无法方便的对action层做单元测试一直是我的心头大恨。现在好了，我们有了StrutsTestCase。按照网上的介绍，StrutsTestCase用起来应该是非常简单的，只要下了jar包回来引用到工程里面就可以了。实际上可能也差不多－－如果你运气不象我这么臭的话。</DIV><DIV class=postText><P><BR>在sourceforge上随便挑其中一个镜象的下载地址：<BR><A href="http://aleron.dl.sourceforge.net/sourceforge/strutstestcase/strutstest213-1.2_2.4.zip"><U><FONT color=#0000ff>http://aleron.dl.sourceforge.net/sourceforge/strutstestcase/strutstest213-1.2_2.4.zip</FONT></U></A></P><P>flashget回来，放到jbuilder的userhome里面，找一个struts action创建test case，创建的时候吧test case的基类改为 MockStrutsTestCase，测试的方法一个都不用选（因为我们是要针对action的具体每一个逻辑分支测试而不是具体的某一个方法）。创建成功后添加一个测试：</P><P>&nbsp; public void testSuccessfulRefresh()<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; setRequestPathInfo("/RefreshSystemData");<BR>&nbsp;&nbsp;&nbsp; actionPerform();<BR>&nbsp;&nbsp;&nbsp; verifyForward("success");<BR>&nbsp; }</P><P>嘿嘿，我精心挑了一个没有参数的action来实验。</P><P>一切看起来很顺利。run test，噩梦开始了：</P><P>java.lang.NullPointerException<BR>&nbsp;at servletunit.struts.MockStrutsTestCase.getActionServlet(MockStrutsTestCase.java:331)<BR>&nbsp;at servletunit.struts.MockStrutsTestCase.tearDown(MockStrutsTestCase.java:130)<BR>&nbsp;at hospital.tongren.oa.system.action.TestRefreshSystemDataAction.tearDown(TestRefreshSystemDataAction.java:34)<BR>...(Click for full stack trace)...</P><P>还好我没有开音箱，不然又是一大炮轰出来。</P><P>看来要调试了，先去同一个地方下了StrutsTestCase原码回来<BR>&nbsp;<A href="http://aleron.dl.sourceforge.net/sourceforge/strutstestcase/strutstest-213-src.zip"><U><FONT color=#0000ff>http://aleron.dl.sourceforge.net/sourceforge/strutstestcase/strutstest-213-src.zip</FONT></U></A></P><P>加进userhome里面的source。debug进去，跟到org.apache.struts.action.ActionServlet里面，出错的地方是：</P><P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; InputStream input =<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getServletContext().getResourceAsStream("/WEB-INF/web.xml");</P><P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; digester.parse(input);</P><P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (IOException e) {<BR>....</P><P>input 为空指针。不知道为什么ServletContextSimulator在模拟ServletContext的时候没能够正确的找到webmodule的位置。上网搜了好一会儿文档，在 <A href="http://strutstestcase.sourceforge.net/api/servletunit/struts/MockStrutsTestCase.html"><U><FONT color=#0000ff>http://strutstestcase.sourceforge.net/api/servletunit/struts/MockStrutsTestCase.html</FONT></U></A> 中发现了这样一段：</P><P>NOTE: By default, the Struts ActionServlet will look for the file WEB-INF/struts-config.xml, so you must place the directory that contains WEB-INF in your CLASSPATH. ...</P><P>先照它说的试试把webmodule路径放进classpath中，没有用。<BR>往下看，发现了这个好东东：setContextDirectory。在startup中加一句：</P><P>this.setContextDirectory(new File("E:\\projectPath\\webModulePath\\"));</P><P>终于把那个空指针给过了。但是报一个新的异常：</P><P>junit.framework.AssertionFailedError: received error 400 : Invalid path /RefreshSystemData was requested</P><P>&nbsp;at servletunit.HttpServletResponseSimulator.sendError(HttpServletResponseSimulator.java:463)</P><P>&nbsp;at org.apache.struts.action.RequestProcessor.processMapping(RequestProcessor.java:684)</P><P>&nbsp;at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:242)</P><P>&nbsp;at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1482)</P><P>&nbsp;at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:525)</P><P>&nbsp;at servletunit.struts.MockStrutsTestCase.actionPerform(MockStrutsTestCase.java:394)</P><P>&nbsp;at hospital.tongren.oa.system.action.TestRefreshSystemDataAction.testSuccessfulRefresh(TestRefreshSystemDataAction.java:51)</P><P>...(Click for full stack trace)...</P><P><BR>找不到strutsconfig里面配置的path。strutsconfig是在web.xml里面配置的，应该还是web.xml没找到造成的，那么就指定strutsconfig文件的位置咯：<BR>&nbsp;&nbsp;&nbsp; setConfigFile("E:\\......\\struts-config.xml");</P><P>终于可以运行起来了。</P><P>随后发现，如果&nbsp; setServletConfigFile("E:\\....\\WEB-INF\\web.xml");的话MockStrutsTestCase也能够根据web.xml中的配置找到strutsconfig文件。</P><P>最后把上面用到的绝对地址E:\\...全部改为相对地址：<BR>&nbsp;&nbsp;&nbsp; setContextDirectory(new File("modulePath\\"));<BR>&nbsp;&nbsp;&nbsp; setServletConfigFile("modulePath\\WEB-INF\\web.xml");<BR>&nbsp;&nbsp;&nbsp; //&nbsp;&nbsp; this.setConfigFile("modulePath\\WEB-INF\\config\\system\\struts-config.xml"); </P><P>血吐完了，继续郁闷，为什么别人都不用配置的这么麻烦呢？到底我做错了什么，还是jbuilder的错？</P><P>CactusStrutsTestCase也没配起，好像要加个什么包吧，再看看先。</P><BR>[<A href="javascript:StorePage()"><U><FONT color=#0000ff>点击此处收藏本文</FONT></U></A>] </DIV><DIV class=postFoot>发表于 2005年01月22日 4:04 PM </DIV></DIV><LINK href="http://blog.csdn.net/emu/Services/Pingback.aspx" rel=pingback><!--<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"xmlns:dc="http://purl.org/dc/elements/1.1/"xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"><rdf:Descriptionrdf:about="http://blog.csdn.net/emu/archive/2005/01/22/263856.aspx"dc:identifier="http://blog.csdn.net/emu/archive/2005/01/22/263856.aspx"dc:title="StrutsTestCase 试用手记"trackback:ping="http://blog.csdn.net/emu/services/trackbacks/263856.aspx" /></rdf:RDF>--><BR><DIV id=comments><H3></H3><DIV class=post><DIV class=postTitle><A href="http://blog.csdn.net/emu/" target=_blank><U><FONT color=#800080>emu</FONT></U></A>&nbsp;发表于<SPAN>2005-01-22 8:00 PM</SPAN>&nbsp;&nbsp;</DIV><DIV class=postText>http://jakarta.apache.org/cactus/getting_started.html 中有详细的说明。感觉确实复杂了一点。现在进展是： <BR><BR>org.apache.cactus.util.ChainedRuntimeException: Failed to get the test results at [http://localhost:8083/TongRenOA/ServletRedirector] <BR><BR>at org.apache.cactus.internal.client.connector.http.DefaultHttpClient.doTest_aroundBody0(DefaultHttpClient.java:92) <BR><BR>at org.apache.cactus.internal.client.connector.http.DefaultHttpClient.doTest_aroundBody1$advice(DefaultHttpClient.java:206) <BR><BR>at org.apache.cactus.internal.client.connector.http.DefaultHttpClient.doTest(DefaultHttpClient.java) <BR><BR>at org.apache.cactus.internal.client.connector.http.HttpProtocolHandler.runWebTest(HttpProtocolHandler.java:159) <BR><BR>at org.apache.cactus.internal.client.connector.http.HttpProtocolHandler.runTest_aroundBody0(HttpProtocolHandler.java:80) <BR><BR>at org.apache.cactus.internal.client.connector.http.HttpProtocolHandler.runTest_aroundBody1$advice(HttpProtocolHandler.java:206) <BR><BR>at org.apache.cactus.internal.client.connector.http.HttpProtocolHandler.runTest(HttpProtocolHandler.java) <BR><BR>at org.apache.cactus.internal.client.ClientTestCaseCaller.runTest(ClientTestCaseCaller.java:144) <BR><BR>at org.apache.cactus.internal.AbstractCactusTestCase.runBareClient(AbstractCactusTestCase.java:215) <BR><BR>at org.apache.cactus.internal.AbstractCactusTestCase.runBare(AbstractCactusTestCase.java:133) <BR><BR>...(Click for full stack trace)... <BR><BR>下班先。</DIV></DIV></DIV><PRE><P></P></PRE><P></P></PRE></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/emu/aggbug/4775.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/emu/" target="_blank">emu</a> 2005-05-18 16:19 <a href="http://www.blogjava.net/emu/articles/4775.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java版的sha-1  和 MD5</title><link>http://www.blogjava.net/emu/articles/4774.html</link><dc:creator>emu</dc:creator><author>emu</author><pubDate>Wed, 18 May 2005 08:16:00 GMT</pubDate><guid>http://www.blogjava.net/emu/articles/4774.html</guid><wfw:comment>http://www.blogjava.net/emu/comments/4774.html</wfw:comment><comments>http://www.blogjava.net/emu/articles/4774.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/emu/comments/commentRss/4774.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/emu/services/trackbacks/4774.html</trackback:ping><description><![CDATA[<TABLE style="BORDER-COLLAPSE: collapse" width="100%" border=0>
<TBODY>
<TR>
<TD class=BlogArticleTitle><A href="http://stonelf.bloglong.com/BlogCollection/MyBlog.aspx?ArticleID=19792">java版的sha-1 </A></TD></TR>
<TR>
<TD class=BlogArticleContent>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 width="100%" border=0>
<TBODY>
<TR>
<TD style="LEFT: 0px; WIDTH: 100%; WORD-WRAP: break-word"><PRE><P> <P>/*<BR>&nbsp;* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined<BR>&nbsp;* in FIPS PUB 180-1<BR>&nbsp;* Copyright (C) Paul Johnston 2000 - 2002.<BR>&nbsp;* See <A href="http://pajhome.org.uk/site/legal.html">http://pajhome.org.uk/site/legal.html</A> for details.<BR>&nbsp;*/</P><P>/*<BR>&nbsp;* Convert a 32-bit number to a hex string with ms-byte first<BR>&nbsp;*/<BR>var hex_chr = "0123456789abcdef";<BR>function hex(num)<BR>{<BR>&nbsp; var str = "";<BR>&nbsp; for(var j = 7; j &gt;= 0; j--)<BR>&nbsp;&nbsp;&nbsp; str += hex_chr.charAt((num &gt;&gt; (j * 4)) &amp; 0x0F);<BR>&nbsp; return str;<BR>}</P><P>/*<BR>&nbsp;* Convert a string to a sequence of 16-word blocks, stored as an array.<BR>&nbsp;* Append padding bits and the length, as described in the SHA1 standard.<BR>&nbsp;*/<BR>function str2blks_SHA1(str)<BR>{<BR>&nbsp; var nblk = ((str.length + 8) &gt;&gt; 6) + 1;<BR>&nbsp; var blks = new Array(nblk * 16);<BR>&nbsp; for(var i = 0; i &lt; nblk * 16; i++) blks[i] = 0;<BR>&nbsp; for(var i = 0; i &lt; str.length; i++)<BR>&nbsp;&nbsp;&nbsp; blks[i &gt;&gt; 2] |= str.charCodeAt(i) &lt;&lt; (24 - (i % 4) * 8);<BR>&nbsp; blks[i &gt;&gt; 2] |= 0x80 &lt;&lt; (24 - (i % 4) * 8);<BR>&nbsp; blks[nblk * 16 - 1] = str.length * 8;<BR>&nbsp; return blks;<BR>}</P><P>/*<BR>&nbsp;* Add integers, wrapping at 2^32. This uses 16-bit operations internally<BR>&nbsp;* to work around bugs in some JS interpreters.<BR>&nbsp;*/<BR>function safe_add(x, y)<BR>{<BR>&nbsp; var lsw = (x &amp; 0xFFFF) + (y &amp; 0xFFFF);<BR>&nbsp; var msw = (x &gt;&gt; 16) + (y &gt;&gt; 16) + (lsw &gt;&gt; 16);<BR>&nbsp; return (msw &lt;&lt; 16) | (lsw &amp; 0xFFFF);<BR>}</P><P>/*<BR>&nbsp;* Bitwise rotate a 32-bit number to the left<BR>&nbsp;*/<BR>function rol(num, cnt)<BR>{<BR>&nbsp; return (num &lt;&lt; cnt) | (num &gt;&gt;&gt; (32 - cnt));<BR>}</P><P>/*<BR>&nbsp;* Perform the appropriate triplet combination function for the current<BR>&nbsp;* iteration<BR>&nbsp;*/<BR>function ft(t, b, c, d)<BR>{<BR>&nbsp; if(t &lt; 20) return (b &amp; c) | ((~b) &amp; d);<BR>&nbsp; if(t &lt; 40) return b ^ c ^ d;<BR>&nbsp; if(t &lt; 60) return (b &amp; c) | (b &amp; d) | (c &amp; d);<BR>&nbsp; return b ^ c ^ d;<BR>}</P><P>/*<BR>&nbsp;* Determine the appropriate additive constant for the current iteration<BR>&nbsp;*/<BR>function kt(t)<BR>{<BR>&nbsp; return (t &lt; 20) ?&nbsp; 1518500249 : (t &lt; 40) ?&nbsp; 1859775393 :<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (t &lt; 60) ? -1894007588 : -899497514;<BR>}</P><P>/*<BR>&nbsp;* Take a string and return the hex representation of its SHA-1.<BR>&nbsp;*/<BR>function calcSHA1(str)<BR>{<BR>&nbsp; var x = str2blks_SHA1(str);<BR>&nbsp; var w = new Array(80);</P><P>&nbsp; var a =&nbsp; 1732584193;<BR>&nbsp; var b = -271733879;<BR>&nbsp; var c = -1732584194;<BR>&nbsp; var d =&nbsp; 271733878;<BR>&nbsp; var e = -1009589776;</P><P>&nbsp; for(var i = 0; i &lt; x.length; i += 16)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; var olda = a;<BR>&nbsp;&nbsp;&nbsp; var oldb = b;<BR>&nbsp;&nbsp;&nbsp; var oldc = c;<BR>&nbsp;&nbsp;&nbsp; var oldd = d;<BR>&nbsp;&nbsp;&nbsp; var olde = e;</P><P>&nbsp;&nbsp;&nbsp; for(var j = 0; j &lt; 80; j++)<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(j &lt; 16) w[j] = x[i + j];<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var t = safe_add(safe_add(rol(a, 5), ft(j, b, c, d)), safe_add(safe_add(e, w[j]), kt(j)));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e = d;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = c;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = rol(b, 30);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = a;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = t;<BR>&nbsp;&nbsp;&nbsp; }</P><P>&nbsp;&nbsp;&nbsp; a = safe_add(a, olda);<BR>&nbsp;&nbsp;&nbsp; b = safe_add(b, oldb);<BR>&nbsp;&nbsp;&nbsp; c = safe_add(c, oldc);<BR>&nbsp;&nbsp;&nbsp; d = safe_add(d, oldd);<BR>&nbsp;&nbsp;&nbsp; e = safe_add(e, olde);<BR>&nbsp; }<BR>&nbsp; return hex(a) + hex(b) + hex(c) + hex(d) + hex(e);<BR>}</P><P><HR><BR><BR><A href="http://stonelf.bloglong.com/BlogCollection/MyBlog.aspx?ArticleID=19791">java版的MD5 </A><BR><BR><BR></P><P></P><BR><BR><BR><BR><BR><BR> <BR><BR> <P>public class MD5<BR>{<BR>&nbsp; /*<BR>&nbsp;&nbsp; * A Java implementation of the RSA Data Security, Inc. MD5 Message<BR>&nbsp;&nbsp; * Digest Algorithm, as defined in RFC 1321.<BR>&nbsp;&nbsp; * Based on the JavaScript implementation of Paul Johnston<BR>&nbsp;&nbsp; *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Copyright (C) Paul Johnston 1999 - 2000.<BR>&nbsp;&nbsp; *&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; See <A href="http://pajhome.org.uk/site/legal.html">http://pajhome.org.uk/site/legal.html</A> for details.<BR>&nbsp;&nbsp; * Java Version by Thomas Weber (Orange Interactive GmbH)<BR>&nbsp;&nbsp; */<BR>&nbsp; <BR>&nbsp; /*<BR>&nbsp;&nbsp; * Convert a 32-bit number to a hex string with ls-byte first<BR>&nbsp;&nbsp; */<BR>&nbsp; String hex_chr = "0123456789abcdef";<BR>&nbsp; private String rhex(int num)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; String str = "";<BR>&nbsp;&nbsp;&nbsp; for(int j = 0; j &lt;= 3; j++)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; str = str + hex_chr.charAt((num &gt;&gt; (j * 8 + 4)) &amp; 0x0F) + hex_chr.charAt((num &gt;&gt; (j * 8)) &amp; 0x0F);<BR>&nbsp;&nbsp;&nbsp; return str;<BR>&nbsp; }<BR>&nbsp; <BR>&nbsp; /*<BR>&nbsp;&nbsp; * Convert a string to a sequence of 16-word blocks, stored as an array.<BR>&nbsp;&nbsp; * Append padding bits and the length, as described in the MD5 standard.<BR>&nbsp;&nbsp; */<BR>&nbsp; private int[] str2blks_MD5(String str)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; int nblk = ((str.length() + 8) &gt;&gt; 6) + 1;<BR>&nbsp;&nbsp;&nbsp; int[] blks = new int[nblk * 16];<BR>&nbsp;&nbsp;&nbsp; int i = 0;<BR>&nbsp;&nbsp;&nbsp; for(i = 0; i &lt; nblk * 16; i++) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; blks[i] = 0;<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; for(i = 0; i &lt; str.length(); i++) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; blks[i &gt;&gt; 2] |= str.charAt(i) &lt;&lt; ((i % 4) * 8);<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; blks[i &gt;&gt; 2] |= 0x80 &lt;&lt; ((i % 4) * 8);<BR>&nbsp;&nbsp;&nbsp; blks[nblk * 16 - 2] = str.length()*8;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; return blks;<BR>&nbsp; }<BR>&nbsp; <BR>&nbsp; /*<BR>&nbsp;&nbsp; * Add integers, wrapping at 2^32<BR>&nbsp;&nbsp; */<BR>&nbsp; private int add(int x, int y)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return ((x&amp;0x7FFFFFFF) + (y&amp;0x7FFFFFFF)) ^ (x&amp;0x80000000) ^ (y&amp;0x80000000);<BR>&nbsp; }<BR>&nbsp; <BR>&nbsp; /*<BR>&nbsp;&nbsp; * Bitwise rotate a 32-bit number to the left<BR>&nbsp;&nbsp; */<BR>&nbsp; private int rol(int num, int cnt)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return (num &lt;&lt; cnt) | (num &gt;&gt;&gt; (32 - cnt));<BR>&nbsp; }<BR>&nbsp; <BR>&nbsp; /*<BR>&nbsp;&nbsp; * These functions implement the basic operation for each round of the<BR>&nbsp;&nbsp; * algorithm.<BR>&nbsp;&nbsp; */<BR>&nbsp; private int cmn(int q, int a, int b, int x, int s, int t)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return add(rol(add(add(a, q), add(x, t)), s), b);<BR>&nbsp; }<BR>&nbsp; private int ff(int a, int b, int c, int d, int x, int s, int t)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return cmn((b &amp; c) | ((~b) &amp; d), a, b, x, s, t);<BR>&nbsp; }<BR>&nbsp; private int gg(int a, int b, int c, int d, int x, int s, int t)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return cmn((b &amp; d) | (c &amp; (~d)), a, b, x, s, t);<BR>&nbsp; }<BR>&nbsp; private int hh(int a, int b, int c, int d, int x, int s, int t)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return cmn(b ^ c ^ d, a, b, x, s, t);<BR>&nbsp; }<BR>&nbsp; private int ii(int a, int b, int c, int d, int x, int s, int t)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return cmn(c ^ (b | (~d)), a, b, x, s, t);<BR>&nbsp; }<BR>&nbsp; <BR>&nbsp; /*<BR>&nbsp;&nbsp; * Take a string and return the hex representation of its MD5.<BR>&nbsp;&nbsp; */<BR>&nbsp; public String calcMD5(String str)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; int[] x = str2blks_MD5(str);<BR>&nbsp;&nbsp;&nbsp; int a = 0x67452301;<BR>&nbsp;&nbsp;&nbsp; int b = 0xEFCDAB89;<BR>&nbsp;&nbsp;&nbsp; int c = 0x98BADCFE;<BR>&nbsp;&nbsp;&nbsp; int d = 0x10325476;<BR>&nbsp; <BR>&nbsp;&nbsp;&nbsp; for(int i = 0; i &lt; x.length; i += 16)<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int olda = a;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int oldb = b;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int oldc = c;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int oldd = d;<BR>&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = ff(a, b, c, d, x[i+ 0], 7 , 0xD76AA478);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = ff(d, a, b, c, x[i+ 1], 12, 0xE8C7B756);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = ff(c, d, a, b, x[i+ 2], 17, 0x242070DB);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = ff(b, c, d, a, x[i+ 3], 22, 0xC1BDCEEE);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = ff(a, b, c, d, x[i+ 4], 7 , 0xF57C0FAF);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = ff(d, a, b, c, x[i+ 5], 12, 0x4787C62A);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = ff(c, d, a, b, x[i+ 6], 17, 0xA8304613);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = ff(b, c, d, a, x[i+ 7], 22, 0xFD469501);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = ff(a, b, c, d, x[i+ 8], 7 , 0x698098D8);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = ff(d, a, b, c, x[i+ 9], 12, 0x8B44F7AF);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = ff(c, d, a, b, x[i+10], 17, 0xFFFF5BB1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = ff(b, c, d, a, x[i+11], 22, 0x895CD7BE);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = ff(a, b, c, d, x[i+12], 7 , 0x6B901122);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = ff(d, a, b, c, x[i+13], 12, 0xFD987193);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = ff(c, d, a, b, x[i+14], 17, 0xA679438E);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = ff(b, c, d, a, x[i+15], 22, 0x49B40821);<BR>&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = gg(a, b, c, d, x[i+ 1], 5 , 0xF61E2562);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = gg(d, a, b, c, x[i+ 6], 9 , 0xC040B340);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = gg(c, d, a, b, x[i+11], 14, 0x265E5A51);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = gg(b, c, d, a, x[i+ 0], 20, 0xE9B6C7AA);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = gg(a, b, c, d, x[i+ 5], 5 , 0xD62F105D);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = gg(d, a, b, c, x[i+10], 9 , 0x02441453);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = gg(c, d, a, b, x[i+15], 14, 0xD8A1E681);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = gg(b, c, d, a, x[i+ 4], 20, 0xE7D3FBC8);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = gg(a, b, c, d, x[i+ 9], 5 , 0x21E1CDE6);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = gg(d, a, b, c, x[i+14], 9 , 0xC33707D6);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = gg(c, d, a, b, x[i+ 3], 14, 0xF4D50D87);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = gg(b, c, d, a, x[i+ 8], 20, 0x455A14ED);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = gg(a, b, c, d, x[i+13], 5 , 0xA9E3E905);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = gg(d, a, b, c, x[i+ 2], 9 , 0xFCEFA3F8);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = gg(c, d, a, b, x[i+ 7], 14, 0x676F02D9);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = gg(b, c, d, a, x[i+12], 20, 0x8D2A4C8A);<BR>&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = hh(a, b, c, d, x[i+ 5], 4 , 0xFFFA3942);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = hh(d, a, b, c, x[i+ 8], 11, 0x8771F681);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = hh(c, d, a, b, x[i+11], 16, 0x6D9D6122);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = hh(b, c, d, a, x[i+14], 23, 0xFDE5380C);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = hh(a, b, c, d, x[i+ 1], 4 , 0xA4BEEA44);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = hh(d, a, b, c, x[i+ 4], 11, 0x4BDECFA9);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = hh(c, d, a, b, x[i+ 7], 16, 0xF6BB4B60);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = hh(b, c, d, a, x[i+10], 23, 0xBEBFBC70);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = hh(a, b, c, d, x[i+13], 4 , 0x289B7EC6);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = hh(d, a, b, c, x[i+ 0], 11, 0xEAA127FA);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = hh(c, d, a, b, x[i+ 3], 16, 0xD4EF3085);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = hh(b, c, d, a, x[i+ 6], 23, 0x04881D05);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = hh(a, b, c, d, x[i+ 9], 4 , 0xD9D4D039);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = hh(d, a, b, c, x[i+12], 11, 0xE6DB99E5);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = hh(c, d, a, b, x[i+15], 16, 0x1FA27CF8);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = hh(b, c, d, a, x[i+ 2], 23, 0xC4AC5665);<BR>&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = ii(a, b, c, d, x[i+ 0], 6 , 0xF4292244);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = ii(d, a, b, c, x[i+ 7], 10, 0x432AFF97);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = ii(c, d, a, b, x[i+14], 15, 0xAB9423A7);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = ii(b, c, d, a, x[i+ 5], 21, 0xFC93A039);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = ii(a, b, c, d, x[i+12], 6 , 0x655B59C3);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = ii(d, a, b, c, x[i+ 3], 10, 0x8F0CCC92);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = ii(c, d, a, b, x[i+10], 15, 0xFFEFF47D);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = ii(b, c, d, a, x[i+ 1], 21, 0x85845DD1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = ii(a, b, c, d, x[i+ 8], 6 , 0x6FA87E4F);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = ii(d, a, b, c, x[i+15], 10, 0xFE2CE6E0);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = ii(c, d, a, b, x[i+ 6], 15, 0xA3014314);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = ii(b, c, d, a, x[i+13], 21, 0x4E0811A1);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = ii(a, b, c, d, x[i+ 4], 6 , 0xF7537E82);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = ii(d, a, b, c, x[i+11], 10, 0xBD3AF235);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = ii(c, d, a, b, x[i+ 2], 15, 0x2AD7D2BB);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = ii(b, c, d, a, x[i+ 9], 21, 0xEB86D391);<BR>&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a = add(a, olda);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = add(b, oldb);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = add(c, oldc);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; d = add(d, oldd);<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; return rhex(a) + rhex(b) + rhex(c) + rhex(d);<BR>&nbsp; }</P><P>}</P><HR><P></P><P>上面是算法的实现代码，不过我们真正用的时候当然不需要那么复杂了，jdk里面早就有了高效的算法实现：</P><P>package myreuse.common.util;</P><P>import sun.misc.BASE64Encoder;<BR>import java.security.MessageDigest;<BR>import java.security.NoSuchAlgorithmException;</P><P>public abstract class MessageDigester<BR>{<BR>&nbsp; static MessageDigest SHA1 ;<BR>&nbsp; static MessageDigest MD5 ;<BR>&nbsp; static {<BR>&nbsp;&nbsp;&nbsp; try{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SHA1 = MessageDigest.getInstance("SHA1");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MD5 = MessageDigest.getInstance("MD5");<BR>&nbsp;&nbsp;&nbsp; } catch (NoSuchAlgorithmException e){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.printStackTrace();<BR>&nbsp;&nbsp;&nbsp; }</P><P>&nbsp; }<BR>&nbsp; public static byte[] SHA1(String msg)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return SHA1.digest(msg.getBytes());<BR>&nbsp; }</P><P>&nbsp; public static byte[] MD5(String msg)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return MD5.digest(msg.getBytes());<BR>&nbsp; }</P><P>&nbsp; private static BASE64Encoder bASE64Encoder = new BASE64Encoder();</P><P>&nbsp; public static String hex(byte[] b)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; StringBuffer result = new StringBuffer(b.length);<BR>&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; b.length; i++)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result.append(Integer.toHexString(0xff &amp; b[i]));<BR>&nbsp;&nbsp;&nbsp; return result.toString();<BR>&nbsp; }</P><P>&nbsp; public static String base64(byte[] b)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; String result = bASE64Encoder.encode(b);<BR>&nbsp;&nbsp;&nbsp; int i = result.indexOf('=');<BR>&nbsp;&nbsp;&nbsp; if (i&gt;-1) result = result.substring(0,i);<BR>&nbsp;&nbsp;&nbsp; return result;<BR>&nbsp; }</P><P>&nbsp; public static String base64SHA1(String msg)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return base64(SHA1(msg));<BR>&nbsp; }</P><P>&nbsp; public static String base64MD5(String msg)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return base64(MD5(msg));<BR>&nbsp; }</P><P>&nbsp; public static String hexSHA1(String msg)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return hex(SHA1(msg));<BR>&nbsp; }</P><P>&nbsp; public static String hexMD5(String msg)<BR>&nbsp; {<BR>&nbsp;&nbsp;&nbsp; return hex(MD5(msg));<BR>&nbsp; }</P><P>}<BR></P><P></P></PRE></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/emu/aggbug/4774.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/emu/" target="_blank">emu</a> 2005-05-18 16:16 <a href="http://www.blogjava.net/emu/articles/4774.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java版本的escape和unescape函数 </title><link>http://www.blogjava.net/emu/articles/4773.html</link><dc:creator>emu</dc:creator><author>emu</author><pubDate>Wed, 18 May 2005 08:14:00 GMT</pubDate><guid>http://www.blogjava.net/emu/articles/4773.html</guid><wfw:comment>http://www.blogjava.net/emu/comments/4773.html</wfw:comment><comments>http://www.blogjava.net/emu/articles/4773.html#Feedback</comments><slash:comments>12</slash:comments><wfw:commentRss>http://www.blogjava.net/emu/comments/commentRss/4773.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/emu/services/trackbacks/4773.html</trackback:ping><description><![CDATA[<TABLE style="BORDER-COLLAPSE: collapse" width="100%" border=0>
<TBODY>
<TR>
<TD class=BlogArticleTitle><A href="http://stonelf.bloglong.com/BlogCollection/MyBlog.aspx?ArticleID=19797">java版本的escape和unescape函数 </A></TD></TR>
<TR>
<TD class=BlogArticleContent>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 width="100%" border=0>
<TBODY>
<TR>
<TD style="LEFT: 0px; WIDTH: 100%; WORD-WRAP: break-word"><PRE><P><PRE><P><PRE><P>&nbsp;</P><P>class&nbsp; EscapeUnescape<BR>{<BR>&nbsp;public static String&nbsp; escape (String src)<BR>&nbsp;{<BR>&nbsp;&nbsp;int i;<BR>&nbsp;&nbsp;char j;<BR>&nbsp;&nbsp;StringBuffer tmp = new StringBuffer();<BR>&nbsp;&nbsp;tmp.ensureCapacity(src.length()*6);</P><P>&nbsp;&nbsp;for (i=0;i&lt;src.length() ;i++ )<BR>&nbsp;&nbsp;{</P><P>&nbsp;&nbsp;&nbsp;j = src.charAt(i);</P><P>&nbsp;&nbsp;&nbsp;if (Character.isDigit(j) || Character.isLowerCase(j) || Character.isUpperCase(j))<BR>&nbsp;&nbsp;&nbsp;&nbsp;tmp.append(j);<BR>&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (j&lt;256)<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;tmp.append( "%" );<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (j&lt;16)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tmp.append( "0" );<BR>&nbsp;&nbsp;&nbsp;&nbsp;tmp.append( Integer.toString(j,16) );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;tmp.append( "%u" );<BR>&nbsp;&nbsp;&nbsp;&nbsp;tmp.append( Integer.toString(j,16) );<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;return tmp.toString();<BR>&nbsp;}</P><P>&nbsp;public static String&nbsp; unescape (String src)<BR>&nbsp;{<BR>&nbsp;&nbsp;StringBuffer tmp = new StringBuffer();<BR>&nbsp;&nbsp;tmp.ensureCapacity(src.length());<BR>&nbsp;&nbsp;int&nbsp; lastPos=0,pos=0;<BR>&nbsp;&nbsp;char ch;<BR>&nbsp;&nbsp;while (lastPos&lt;src.length())<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;pos = src.indexOf("%",lastPos);<BR>&nbsp;&nbsp;&nbsp;if (pos == lastPos)<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (src.charAt(pos+1)=='u')<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ch = (char)Integer.parseInt(src.substring(pos+2,pos+6),16);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tmp.append(ch);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lastPos = pos+6;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ch = (char)Integer.parseInt(src.substring(pos+1,pos+3),16);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tmp.append(ch);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lastPos = pos+3;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (pos == -1)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tmp.append(src.substring(lastPos));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lastPos=src.length();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tmp.append(src.substring(lastPos,pos));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lastPos=pos;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;return tmp.toString();<BR>&nbsp;}</P><P>&nbsp;public static void main(String[] args) <BR>&nbsp;{<BR>&nbsp;&nbsp;String tmp="<A href="mailto:~!@#$%^&amp;*()_+|\\=-,./?><;'][{}\"><U><FONT color=#0000ff>~!@#$%^&amp;*()_+|\\=-,./?&gt;&lt;;'][{}\</FONT></U></A>"";<BR>&nbsp;&nbsp;System.out.println("testing escape : "+tmp);<BR>&nbsp;&nbsp;tmp =escape(tmp);<BR>&nbsp;&nbsp;System.out.println(tmp);<BR>&nbsp;&nbsp;System.out.println("testing unescape :"+tmp);<BR>&nbsp;&nbsp;System.out.println(unescape(tmp));<BR>&nbsp;}<BR>}</P></PRE></PRE></PRE></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/emu/aggbug/4773.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/emu/" target="_blank">emu</a> 2005-05-18 16:14 <a href="http://www.blogjava.net/emu/articles/4773.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>解析csv格式的java函数 </title><link>http://www.blogjava.net/emu/articles/4772.html</link><dc:creator>emu</dc:creator><author>emu</author><pubDate>Wed, 18 May 2005 08:13:00 GMT</pubDate><guid>http://www.blogjava.net/emu/articles/4772.html</guid><wfw:comment>http://www.blogjava.net/emu/comments/4772.html</wfw:comment><comments>http://www.blogjava.net/emu/articles/4772.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/emu/comments/commentRss/4772.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/emu/services/trackbacks/4772.html</trackback:ping><description><![CDATA[<TABLE style="BORDER-COLLAPSE: collapse" width="100%" border=0>
<TBODY>
<TR>
<TD class=BlogArticleTitle><A href="http://stonelf.bloglong.com/BlogCollection/MyBlog.aspx?ArticleID=19798">解析csv格式的java函数 </A></TD></TR>
<TR>
<TD class=BlogArticleContent>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 width="100%" border=0>
<TBODY>
<TR>
<TD style="LEFT: 0px; WIDTH: 100%; WORD-WRAP: break-word"><PRE><P>&nbsp;<P>我们经常将Excel格式的文件保存为csv格式以方便上传和修改，可是当数据中包含逗号和双引号的时候Excel会把该字段用双引号括住并把数据中的"改为""，从而给解析带来了困难。我写了以下函数来解析这样的字符串：</P><P>testSplitCSV.java:</P><P>import java.util.Vector;<BR>class&nbsp; testSplitCSV{<BR>&nbsp;/**<BR>&nbsp;*&nbsp;Split one line of csv file<BR>&nbsp;*&nbsp;@return&nbsp;a String array results<BR>&nbsp;*/<BR>&nbsp;public static String[] splitCSV(String src)&nbsp;throws Exception{<BR>&nbsp;&nbsp;if (src==null || src.equals("")) return&nbsp;new&nbsp;String[0];<BR>&nbsp;&nbsp;StringBuffer st=new StringBuffer();<BR>&nbsp;&nbsp;Vector result=new Vector();<BR>&nbsp;&nbsp;boolean&nbsp;beginWithQuote = false;<BR>&nbsp;&nbsp;for&nbsp;(int i=0;i&lt;src.length();i++){<BR>&nbsp;&nbsp;&nbsp;char ch&nbsp;= src.charAt(i);<BR>&nbsp;&nbsp;&nbsp;if (ch=='\"'){<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (beginWithQuote){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i++;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (i&gt;=src.length()){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result.addElement(st.toString());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;st=new StringBuffer();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;beginWithQuote=false;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ch=src.charAt(i);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (ch == '\"'){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;st.append(ch);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else if (ch ==&nbsp;','){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result.addElement(st.toString());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;st=new StringBuffer();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;beginWithQuote =&nbsp;false;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new Exception("Single&nbsp;double-quote char mustn't exist&nbsp;in filed "+(result.size()+1)+" while it&nbsp;is begined with&nbsp;quote\nchar&nbsp;at:"+i);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}else if (st.length()==0){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;beginWithQuote =&nbsp;true;<BR>&nbsp;&nbsp;&nbsp;&nbsp;}else{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new Exception("Quote cannot exist in a filed which doesn't begin with quote!\nfield:"+(result.size()+1));<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;}else if (ch==','){<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (beginWithQuote){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;st.append(ch);<BR>&nbsp;&nbsp;&nbsp;&nbsp;}else{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result.addElement(st.toString());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;st=new StringBuffer();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;beginWithQuote =&nbsp;false;<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;}else{<BR>&nbsp;&nbsp;&nbsp;&nbsp;st.append(ch);<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;if (st.length()!=0){<BR>&nbsp;&nbsp;&nbsp;if (beginWithQuote){<BR>&nbsp;&nbsp;&nbsp;&nbsp;throw new Exception("last field is begin with but not end with double quote");<BR>&nbsp;&nbsp;&nbsp;}else{<BR>&nbsp;&nbsp;&nbsp;&nbsp;result.addElement(st.toString());<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;String&nbsp;rs[] = new String[result.size()];<BR>&nbsp;&nbsp;for (int i=0;i&lt;rs.length;i++){<BR>&nbsp;&nbsp;&nbsp;rs[i]=(String)result.elementAt(i);<BR>&nbsp;&nbsp;}<BR>&nbsp;return&nbsp;rs;<BR>&nbsp;}</P><P>&nbsp;public static void main(String[] args){<BR>&nbsp;&nbsp;String src1=&nbsp; "\"fh,zg\",sdf,\"asfs,\",\",dsdf\",\"aadf\"\"\",\"\"\"hdfg\",\"fgh\"\"dgnh\",hgfg'dfh,\"asdfa\"\"\"\"\",\"\"\"\"\"fgjhg\",\"gfhg\"\"\"\"hb\"";<BR>&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;String[] Ret = splitCSV(src1);<BR>&nbsp;&nbsp;&nbsp;for (int i=0;i&lt;Ret.length;i++){<BR>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(i+": "+Ret[i]);<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;catch(Exception e) {<BR>&nbsp;&nbsp;&nbsp;e.printStackTrace();<BR>&nbsp;&nbsp;}<BR>&nbsp;}<BR>}</P></PRE></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/emu/aggbug/4772.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/emu/" target="_blank">emu</a> 2005-05-18 16:13 <a href="http://www.blogjava.net/emu/articles/4772.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>写了一个hta来转换资源文件格式  </title><link>http://www.blogjava.net/emu/articles/4769.html</link><dc:creator>emu</dc:creator><author>emu</author><pubDate>Wed, 18 May 2005 08:07:00 GMT</pubDate><guid>http://www.blogjava.net/emu/articles/4769.html</guid><wfw:comment>http://www.blogjava.net/emu/comments/4769.html</wfw:comment><comments>http://www.blogjava.net/emu/articles/4769.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/emu/comments/commentRss/4769.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/emu/services/trackbacks/4769.html</trackback:ping><description><![CDATA[<TABLE style="BORDER-COLLAPSE: collapse" width="95%" border=0>
<TBODY>
<TR>
<TD class=BlogArticleTitle><SPAN id=BlogBody_BlogArticleView_lbTitle></SPAN></TD></TR>
<TR>
<TD class=BlogArticleContent>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 width="100%" border=0>
<TBODY>
<TR>
<TD style="LEFT: 0px; WIDTH: 100%; WORD-WRAP: break-word"><SPAN id=BlogBody_BlogArticleView_lbContent><PRE><P><DIV align=center><PRE><A href="http://blog.csdn.net/emu/archive/2004/11/13/179977.aspx"><U><FONT color=#800080>写了一个hta来转换资源文件格式</FONT></U></A> </PRE></DIV><DIV align=left><PRE><P>java的资源文件一个设计目的就是方便的提供多语言支持，可是它本身对unicode的支持是十分搞笑的。很多人见到这样的资源文件都会觉得很熟悉吧：<BR></P><P>tongren.oa.system.user.userNameExists=\u7528\u6237\u540D\u5DF2\u7ECF\u5B58\u5728\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u4E00\u4E2A\u7528\u6237\u540D<BR>tongren.oa.system.role.roleNameExists=\u89D2\u8272\u540D\u5DF2\u7ECF\u5B58\u5728\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u4E00\u4E2A\u89D2\u8272\u540D<BR>tongren.oa.system.role.UserAssignedToRole=\u6307\u5B9A\u89D2\u8272\u4E0D\u80FD\u5220\u9664\uFF0C\u56E0\u4E3A\u7528\u6237{0}\u62E5\u6709\u8FD9\u4E2A\u89D2\u8272<BR><BR></P><P>我的天，这要怎么读？还要用工具把它转回unicode才能看到汉字，修改完了还要转回去？<BR></P><P><BR>昨天下午抽了一点时间写了一个hta工具来编辑资源文件，现在终于可以直接修改，自动转换保存了。直接把下面的代码保存为一个后缀为hta的文件即可：<BR></P><P>&lt;html&gt;<BR>&lt;head&gt;<BR>&lt;title&gt;&lt;/title&gt;<BR>&lt;/head&gt;<BR>&lt;body&gt;<BR>&lt;input type=file onchange="getFile()" id=fileSelector&gt;<BR>&lt;SCRIPT LANGUAGE="JavaScript"&gt;<BR>&lt;!--<BR>var fso;<BR>var OpenFileForReading = 1 <BR>var OpenFileForWriting = 2 <BR>var OpenFileForAppending = 8 <BR>var srcFilePath="";<BR>fso = new ActiveXObject("Scripting.FileSystemObject");</P><P>function getFile(){<BR>&nbsp;if (!fso.FileExists(fileSelector.value)){<BR>&nbsp;&nbsp;alert("指定文件不存在或已经被移除");<BR>&nbsp;&nbsp;event.returnValue=false;<BR>&nbsp;&nbsp;return;<BR>&nbsp;}<BR>&nbsp;srcFilePath = fileSelector.value;<BR>&nbsp;var textStream = fso.OpentextFile(srcFilePath, OpenFileForReading,true);<BR>&nbsp;viewer.value = textStream.ReadAll();<BR>&nbsp;textStream.Close();<BR>&nbsp;document.getElementsByName("showType")[0].checked=true;<BR>&nbsp;buttonSaveAs.disabled=false;<BR>&nbsp;buttonSave.disabled=false;<BR>&nbsp;showAsText();<BR>}<BR>function showAsText(){<BR>&nbsp;document.getElementsByName("showType")[1].checked=true;<BR>&nbsp;viewer.value = unescape(viewer.value.replace(/\\u/g,"%u"))<BR>}<BR>function showAsProperties(){<BR>&nbsp;document.getElementsByName("showType")[0].checked=true;<BR>&nbsp;viewer.value = unescape(escape(viewer.value).replace(/%u/g,"<A href="file://u/"><U><FONT color=#0000ff>\\u</FONT></U></A>"))<BR>}<BR>function saveFile(){<BR>&nbsp;showAsProperties();<BR>&nbsp;var fileStillExists = fso.FileExists(srcFilePath);<BR>&nbsp;if (fileStillExists)<BR>&nbsp;&nbsp;fso.CopyFile (srcFilePath,fileSelector.value+".bak");<BR>&nbsp;var textStream = fso.OpentextFile(srcFilePath, OpenFileForWriting,true);<BR>&nbsp;textStream.Write(viewer.value);<BR>&nbsp;textStream.Close();<BR>&nbsp;alert("资源文件已经成功保存"+(fileStillExists?("，原来的文件备份为\n"+srcFilePath+".bak"):"!"));<BR>}<BR>function saveAs(){<BR>&nbsp;showAsProperties();<BR>&nbsp;var srcFileName = srcFilePath.substr(srcFilePath.lastIndexOf("<A href="file://%22)+1/"><U><FONT color=#0000ff>\\")+1</FONT></U></A>);<BR>&nbsp;var newFileName = prompt("请输入新文件名",srcFileName);<BR>&nbsp;var newFilePath = srcFilePath.replace(srcFileName,newFileName);<BR>&nbsp;if (srcFileName == newFileName){<BR>&nbsp;&nbsp;if (confirm("您确认要覆盖原来的文件？")) saveFile();<BR>&nbsp;}else if (fso.FileExists(newFilePath)){<BR>&nbsp;&nbsp;if (confirm("您确认要覆盖文件"+newFilePath+"？")) {<BR>&nbsp;&nbsp;&nbsp;var textStream = fso.OpentextFile(newFilePath, OpenFileForWriting,true);<BR>&nbsp;&nbsp;&nbsp;textStream.Write(viewer.value);<BR>&nbsp;&nbsp;&nbsp;textStream.Close();<BR>&nbsp;&nbsp;&nbsp;srcFilePath = newFilePath;<BR>&nbsp;&nbsp;&nbsp;alert("资源文件已经成功保存")<BR>&nbsp;&nbsp;}<BR>&nbsp;}else {<BR>&nbsp;&nbsp;var textStream = fso.OpentextFile(newFilePath, OpenFileForWriting,true);<BR>&nbsp;&nbsp;textStream.Write(viewer.value);<BR>&nbsp;&nbsp;textStream.Close();<BR>&nbsp;&nbsp;fileSelector = fileSelector.value;<BR>&nbsp;&nbsp;srcFilePath = newFilePath;<BR>&nbsp;&nbsp;alert("资源文件已经成功保存")<BR>&nbsp;}<BR>}<BR>setInterval("document.title='当前文件：'+srcFilePath",500);<BR>//--&gt;<BR>&lt;/SCRIPT&gt;<BR>&lt;textarea id=viewer style="width:90%;height:90%"&gt;&lt;/textarea&gt;<BR>&lt;BR&gt;</P><P>&lt;input type=radio name="showType" id="asProperties" onclick="showAsProperties()"&gt;<BR>&lt;label for="asProperties"&gt;资源文件格式&lt;/label&gt;</P><P>&lt;input type=radio name="showType" id="asText" onclick="showAsText()"&gt;<BR>&lt;label for="asText"&gt;文本格式&lt;/label&gt;<BR>&lt;input type=button value="保存文件" onclick="saveFile()" id=buttonSave disabled&gt;<BR>&lt;input type=button value="另存为文件" onclick="saveAs()" id=buttonSaveAs disabled&gt;<BR>&lt;/body&gt;<BR>&lt;/html&gt;</P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><P class=postText><FONT size=1></FONT></P><DIV class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: center; mso-pagination: widow-orphan" align=center><SPAN lang=EN-US style="FONT-SIZE: 10pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: Arial"><FONT size=1><HR align=center width="100%" SIZE=2></FONT></SPAN></DIV><P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align=left><FONT size=1><SPAN lang=EN-US style="FONT-SIZE: 9pt; FONT-FAMILY: Arial; mso-font-kerning: 0pt">&nbsp;</P><DIV class=post><DIV class=postTitle><A href="http://kukoo.blogspirit.com/" target=_blank><U><FONT color=#0000ff>kukoo</FONT></U></A>&nbsp;发表于<SPAN>2004-12-24 4:50 PM</SPAN>&nbsp;&nbsp;</DIV><DIV class=postText>Looks nice, but 为什么不用JDK自己的native2ascii来转换？</DIV></DIV><P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align=left><BR></P><DIV class=post><DIV class=postTitle><A href="http://blog.csdn.net/emu/" target=_blank><U><FONT color=#800080>emu</FONT></U></A>&nbsp;发表于<SPAN>2004-12-27 12:17 PM</SPAN>&nbsp;&nbsp;</DIV><DIV class=postText>因为native2ascii只能单向转换，又没有可视化编辑功能，你要维护一个原始版本，每次修改原始版本后重新native2ascii ...</DIV></DIV><P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align=left><BR></P><DIV class=post><DIV class=postTitle>native2ascii是双向的。&nbsp;发表于<SPAN>2004-12-29 6:10 PM</SPAN>&nbsp;&nbsp;</DIV><DIV class=postText>Usage: native2ascii [-reverse] [-encoding encoding] [inputfile [outputfile]]</DIV></DIV><P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align=left><BR></P><DIV class=post><DIV class=postTitle>是双向的&nbsp;发表于<SPAN>2004-12-29 6:10 PM</SPAN>&nbsp;&nbsp;</DIV><DIV class=postText>Native2Ascii是双向的！ <BR><BR>Usage: native2ascii [-reverse] [-encoding encoding] [inputfile [outputfile]]</DIV></DIV><P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align=left><BR></P><DIV class=post><DIV class=postTitle>emu&nbsp;发表于<SPAN>2004-12-30 12:08 PM</SPAN>&nbsp;&nbsp;</DIV><DIV class=postText>哈哈，原来可以转来转去的啊</DIV></DIV></SPAN></FONT><P>&nbsp;</P></PRE></DIV><P></P></PRE></SPAN></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/emu/aggbug/4769.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/emu/" target="_blank">emu</a> 2005-05-18 16:07 <a href="http://www.blogjava.net/emu/articles/4769.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>struts 的radio和checkbox标记的增强 </title><link>http://www.blogjava.net/emu/articles/4758.html</link><dc:creator>emu</dc:creator><author>emu</author><pubDate>Wed, 18 May 2005 06:20:00 GMT</pubDate><guid>http://www.blogjava.net/emu/articles/4758.html</guid><wfw:comment>http://www.blogjava.net/emu/comments/4758.html</wfw:comment><comments>http://www.blogjava.net/emu/articles/4758.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/emu/comments/commentRss/4758.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/emu/services/trackbacks/4758.html</trackback:ping><description><![CDATA[<DIV class=postTitle><A href="http://blog.csdn.net/emu/archive/2005/04/30/369413.aspx"><U><FONT color=#800080>struts 的radio标记的增强</FONT></U></A>
<SCRIPT language=javascript>document.title="struts 的radio标记的增强 - "+document.title</SCRIPT>
 </DIV>
<DIV class=postText>
<P>写struts的html标记的人不知道是不是对html标记不熟，写出来的标记怎么怪里怪气的，id标记被用来绑定bean了，我还以为不支持html的id属性了，不小心看到org.apache.struts.taglib.html.BaseHandlerTag的prepareStyles()方法里面，居然赫然写着：<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (styleId != null) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; styles.append(" id=\"");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; styles.append(getStyleId());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; styles.append("\"");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>原来要用styleId属性来定义html的id属性，不看源码你敢这么猜吗，怎么看styleId都象是个样式有关的属性嘛，要定义html的id属性，如果不能用id至少也应该叫HTMLID或者tagId之类的名字嘛。ok，源码都看了，那我们用styleId也成，可是看看radio标记就实在是笨了：<BR>&nbsp;&nbsp;&nbsp; public int doAfterBody() throws JspException {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (this.bodyContent != null) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String value = this.bodyContent.getString().trim();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (value.length() &gt; 0) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.text = value;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (SKIP_BODY);<BR>&nbsp;&nbsp;&nbsp; }<BR>你说我把文字括到&lt;html:radio&gt;text&lt;/html:radio&gt;标记中间为的是什么？当然这个文字要和radio绑定啦。可是上面的代码居然直接把文字往生成的&lt;input type="radio"...&gt;标记后面一丢了事，有这么不负责的嘛？<BR>还好咱java和html都还懂一点。自己写个类：<BR>package com.reuse.common.web.tag;<BR>import javax.servlet.jsp.JspException;<BR>public class RadioTag extends org.apache.struts.taglib.html.RadioTag{<BR>&nbsp; public int doAfterBody() throws JspException {<BR>&nbsp;&nbsp;&nbsp; if (this.bodyContent != null) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String value = this.bodyContent.getString().trim();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (value.length() &gt; 0) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.text = "&lt;label for=\""+getStyleId()+"\" "+<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; prepareEventHandlers()+<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; prepareStyles().replaceAll(" id=\""+getStyleId()+"\"","")+<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "&gt;"+value+"&lt;/label&gt;";<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; super.setStyleId(null);<BR>&nbsp;&nbsp;&nbsp; return (SKIP_BODY);<BR>&nbsp; }<BR>&nbsp; static int idCount=0;<BR>&nbsp; public int doStartTag() throws JspException {<BR>&nbsp;&nbsp;&nbsp; if (super.getStyleId() == null){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.setStyleId("RadioTag_"+ ++idCount);<BR>&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; return super.doStartTag();<BR>&nbsp; }<BR>}<BR>然后再在WEB-INF下面找到struts-html.tld,把<BR>&lt;tagclass&gt;org.apache.struts.taglib.html.RadioTag&lt;/tagclass&gt;<BR>改为<BR>&lt;tagclass&gt;myPackage.RadioTag&lt;/tagclass&gt;</P>
<P>这下整个世界都清凉多了。回家过五一了，下次再对checkbox开刀吧</P>
<P>
<HR>

<P></P>
<P>
<TABLE class=diary_bg1 cellSpacing=3 cellPadding=3 width="100%" border=0>
<TBODY>
<TR>
<TD><FONT class=diary_title>struts&nbsp;的checkbox标记的增强</FONT></TD></TR></TBODY></TABLE></P>
<P>五一过完，checkbox也开了刀了：</P>
<P>package com.reuse.common.web.tag;</P>
<P>import javax.servlet.jsp.JspException;<BR>public class CheckboxTag<BR>&nbsp;&nbsp;&nbsp; extends org.apache.struts.taglib.html.CheckboxTag<BR>{<BR>&nbsp; public int doAfterBody() throws JspException {<BR>&nbsp; if (this.bodyContent != null) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String value = this.bodyContent.getString().trim();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (value.length() &gt; 0) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.text = "&lt;label for=\""+getStyleId()+"\" "+<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; prepareEventHandlers()+<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; prepareStyles().replaceAll(" id=\""+getStyleId()+"\"","")+<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "&gt;"+value+"&lt;/label&gt;";<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp; }<BR>&nbsp; super.setStyleId(null);<BR>&nbsp; return (SKIP_BODY);<BR>}<BR>static int idCount=0;<BR>public int doStartTag() throws JspException {<BR>&nbsp; if (super.getStyleId() == null){<BR>&nbsp;&nbsp;&nbsp; super.setStyleId("CheckBoxTag_"+ ++idCount);<BR>&nbsp;&nbsp; }<BR>&nbsp; return super.doStartTag();<BR>}</P>
<P>}</P></DIV><img src ="http://www.blogjava.net/emu/aggbug/4758.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/emu/" target="_blank">emu</a> 2005-05-18 14:20 <a href="http://www.blogjava.net/emu/articles/4758.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用junitperf做并发测试带来的问题  </title><link>http://www.blogjava.net/emu/articles/4755.html</link><dc:creator>emu</dc:creator><author>emu</author><pubDate>Wed, 18 May 2005 06:13:00 GMT</pubDate><guid>http://www.blogjava.net/emu/articles/4755.html</guid><wfw:comment>http://www.blogjava.net/emu/comments/4755.html</wfw:comment><comments>http://www.blogjava.net/emu/articles/4755.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/emu/comments/commentRss/4755.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/emu/services/trackbacks/4755.html</trackback:ping><description><![CDATA[<TABLE style="BORDER-COLLAPSE: collapse" width="95%" border=0>
<TBODY>
<TR>
<TD class=BlogArticleTitle><SPAN id=BlogBody_BlogArticleView_lbTitle></SPAN></TD></TR>
<TR>
<TD class=BlogArticleContent>
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 width="100%" border=0>
<TBODY>
<TR>
<TD style="LEFT: 0px; WIDTH: 100%; WORD-WRAP: break-word"><SPAN id=BlogBody_BlogArticleView_lbContent><PRE><PRE><DIV class=postTitle><A href="http://blog.csdn.net/emu/archive/2005/04/26/363405.aspx"><U><FONT color=#800080>用junitperf做并发测试带来的问题</FONT></U></A> </DIV><DIV class=postText><P align=center><FONT size=5>关于junitperf的一点介绍</FONT></P><P><A href="http://www.clarkware.com/software/JUnitPerf.html"><U><FONT color=#800080>junitperf</FONT></U></A>&nbsp;是个很小巧的java性能测试框架，可以在<A href="http://sourceforge.net/project/showfiles.php?group_id=15278"><U><FONT color=#0000ff>http://sourceforge.net/project/showfiles.php?group_id=15278</FONT></U></A>&nbsp;上下载到。可以很容易的把它结合junit一起测试，比如在测试套件里面加这么几行：</P><P>import com.clarkware.junitperf.TimedTest;<BR>import com.clarkware.junitperf.LoadTest;<BR>.......</P><P>&nbsp;&nbsp;&nbsp; int testTimes = 10;<BR>&nbsp;&nbsp;&nbsp; int users = 5;</P><P>&nbsp;&nbsp;&nbsp; suite.addTest(new TestUserDAO("test1AddUser")); //基本功能测试，同时初始化环境<BR>&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; suite.addTest(new TimedTest(new TestUserDAO("test1AddUser"), 1000)); //基本性能测试:方法应在1秒内完成</P><P>&nbsp;&nbsp;&nbsp; suite.addTest(new LoadTest(new TestUserDAO("test1AddUser"), users)); // 并发测试</P><P>&nbsp; suite.addTest(new LoadTest(new TestUserDAO("test1AddUser"),users,testTimes)); //并发负载测试</P><P>&nbsp; suite.addTest(new TimedTest(new LoadTest(new TestUserDAO("test1AddUser"),<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; users,testTimes), 35000)); //并发性能测试<BR></P><P>如果只需要反复做一个测试而不需要并发测试，可以</P><P>suite.addTest(new LoadTest(new TestUserDAO("test1AddUser"),1,testTimes)); </P><P>当然也可以不用junitperf，junit.extensions.RepeatedTest就是设计来干这个的：</P><P>suite.addTest(new RepeatedTest(new TestUserDAO("test1AddUser"), testTimes)); //重复测试<BR></P><P align=center><FONT size=5><BR>用junitperf做并发测试带来的问题<BR></FONT></P><P align=left></P><P align=left><FONT size=2></FONT></P><FONT size=2><BR>做并发测试的时候junitperf有一个问题。注意看这一行：</FONT> <P></P><P align=left><FONT size=2>new LoadTest(new TestUserDAO("test1AddUser"), users)</FONT></P><P align=left><FONT size=2>我们只传递了一个TestUserDAO实例给LoadTest，却要求它开启users个线程来测试，这样这users个线程就会只针对同一个TestUserDAO实例进行测试。这个时候，我们在TestUserDAO里面就不能存放任何状态数据了。比如以前我很喜欢这么做：</FONT></P><P align=left><FONT size=2>public class TestUserDAO extends TestCase{<BR>&nbsp;private int lastId;<BR>&nbsp;protected void setUp() throws Exception{<BR>&nbsp;&nbsp;super.setUp();<BR>&nbsp;&nbsp;//构造一个测试用的数据<BR>&nbsp;&nbsp;User user = new User("张三");<BR>&nbsp;&nbsp;//向数据库插入一条记录<BR>&nbsp;&nbsp;userDAO.addUser(user);<BR>&nbsp;&nbsp;lastId = user.getId();//刚刚插入的记录在数据库中产生的ID;<BR>&nbsp;}</FONT></P><P align=left><FONT size=2>&nbsp;public void testUpdateUser() throws DaoException{<BR>&nbsp;&nbsp;//针对setup中插入的数据进行update操作<BR>&nbsp;&nbsp;user = userDAO.getUserById(lastId);<BR>&nbsp;&nbsp;user.setName("李四");<BR>&nbsp;&nbsp;userDAO.updateUser(user);<BR>&nbsp;}<BR>&nbsp;protected void tearDown() throws Exception{<BR>&nbsp;&nbsp;//删除测试数据<BR>&nbsp;&nbsp;userDAO.deleteUserById(lastId);<BR>&nbsp;&nbsp;super.tearDown();<BR>&nbsp;}<BR></FONT><FONT size=2>这样我是通过一个int变量lastId在各个方法之间传递被测试的数据的。如果用junitperf来测试，lastId变量就会被后来的线程覆盖，导致测试失败。<BR></FONT></P><P align=center><FONT size=5><BR>解决方法<BR></FONT></P><P align=left><FONT size=2><BR>在com.clarkware.junitperf.TestFactory的文档中对这个问题做了说明：</FONT></P><P align=left><FONT size=2>This factory class should be used in cases when a stateful test&nbsp;is intended to be decorated by a &lt;code&gt;LoadTest&lt;/code&gt;.&nbsp; A stateful&nbsp;test is defined as any test that defines test-specific state in&nbsp;its &lt;code&gt;setUp()&lt;/code&gt; method.</FONT></P><P align=left>TestFactory的使用方法是这样：</P><P align=left>import com.clarkware.junitperf.TestFactory;</P><P align=left>......</P><P align=left>&nbsp;&nbsp;&nbsp;&nbsp; suite.addTest(new LoadTest(new TestFactory(TestUserDAO.class), users,testTimes)); //并发负载测试<BR></P><P align=left>但是这样只能观察整个测试类的表现。如果我们要单个的测试测试类中的一个测试，那么可以考虑另一种方法。我们在TestFactory的文档中看到：</P><P align=left>&nbsp;This class is dependent on Java 2.&nbsp; For earlier platforms a&nbsp;&nbsp;local cache implementation should be changed to use, for example,&nbsp;&nbsp;a HashMap to track thread-local information.<BR></P><P align=left>这个方法同时也适用于我们需要处理的情况：</P><P align=left><FONT size=2>public class TestUserDAO extends TestCase{<BR>&nbsp;private static final ThreadLocal threadLocal = new ThreadLocal();<BR>&nbsp;protected void setUp() throws Exception{<BR>&nbsp;&nbsp;super.setUp();<BR>&nbsp;&nbsp;//构造一个测试用的数据<BR>&nbsp;&nbsp;User user = new User("张三");<BR>&nbsp;&nbsp;//向数据库插入一条记录<BR>&nbsp;&nbsp;userDAO.addUser(user);<BR>&nbsp;&nbsp;//lastId = user.getId();//刚刚插入的记录在数据库中产生的ID;<BR>&nbsp;&nbsp;threadLocal.set(new Integer(user.getId()));<BR>&nbsp;}</FONT></P><P align=left><FONT size=2>&nbsp;public void testUpdateUser() throws DaoException{<BR>&nbsp;&nbsp;//针对setup中插入的数据进行update操作<BR>&nbsp;&nbsp;//user = userDAO.getUserById(lastId);<BR>&nbsp;&nbsp;user = userDAO.getUserById(((Integer)threadLocal.get()).intValue());<BR>&nbsp;&nbsp;user.setName("李四");<BR>&nbsp;&nbsp;userDAO.updateUser(user);<BR>&nbsp;}<BR>&nbsp;protected void tearDown() throws Exception{<BR>&nbsp;&nbsp;//删除测试数据<BR>&nbsp;&nbsp;userDAO.deleteUserById(((Integer)threadLocal.get()).intValue());<BR>&nbsp;&nbsp;super.tearDown();<BR>&nbsp;}</FONT></P></DIV><PRE><P></P></PRE><P></P></PRE><P></P></PRE></SPAN></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/emu/aggbug/4755.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/emu/" target="_blank">emu</a> 2005-05-18 14:13 <a href="http://www.blogjava.net/emu/articles/4755.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>自作聪明的junit.swingui.TestRunner </title><link>http://www.blogjava.net/emu/articles/4754.html</link><dc:creator>emu</dc:creator><author>emu</author><pubDate>Wed, 18 May 2005 06:08:00 GMT</pubDate><guid>http://www.blogjava.net/emu/articles/4754.html</guid><wfw:comment>http://www.blogjava.net/emu/comments/4754.html</wfw:comment><comments>http://www.blogjava.net/emu/articles/4754.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/emu/comments/commentRss/4754.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/emu/services/trackbacks/4754.html</trackback:ping><description><![CDATA[<PRE><DIV class=post><DIV class=postTitle><A href="http://blog.csdn.net/emu/archive/2005/04/29/367679.aspx"><U><FONT color=#800080>自作聪明的junit.swingui.TestRunner</FONT></U></A><SCRIPT language=javascript>document.title="自作聪明的junit.swingui.TestRunner - "+document.title</SCRIPT> </DIV><DIV class=postText><P>问题<BR>在junit.swingui.TestRunner的时候发现TestRunner启动过程中报错：<BR>log4j:ERROR A "org.apache.log4j.ConsoleAppender" object is not assignable to a "org.apache.log4j.Appender" variable.<BR>同时也发现一个平时工作正常的类在使用junit.swingui.TestRunner进行测试的时候报告一个奇怪的 ClassCastException，明明构造的对象的类是实现了指定的接口的，可是就是无法造型到接口上。<BR>进一步研究发现，即使造型回原来的类也不行，虽然调试的时候显示构造的对象就是指定的类，但是就是无法造型成这个类，一度认为是妖人作祟或者机子被落了降头。</P><P>研究<BR>求得庄老大再次出手，一下指出指出问题在于不同的类装载器装载的类无法相互造型的。于是进去junit.swingui.TestRunner里面去找类装载器，一翻折腾之后终于找到：</P><P>junit.runner.BaseTestRunner<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |－－－－－－junit.swingui.TestRunner<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |－－－－－－junit.textui.TestRunner</P><P>在BaseTestRunner里面定义了这样一个方法：<BR>&nbsp;&nbsp;&nbsp; public TestSuiteLoader getLoader() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (useReloadingTestSuiteLoader())<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new ReloadingTestSuiteLoader();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new StandardTestSuiteLoader();<BR>&nbsp;&nbsp;&nbsp; }<BR>不过注意到junit.textui.TestRunner是不会出上面的错误的，因为它自己重载了getLoader()方法，<BR>&nbsp;&nbsp;&nbsp; /**<BR>&nbsp;&nbsp;&nbsp;&nbsp; * Always use the StandardTestSuiteLoader. Overridden from<BR>&nbsp;&nbsp;&nbsp;&nbsp; * BaseTestRunner.<BR>&nbsp;&nbsp;&nbsp;&nbsp; */<BR>&nbsp;&nbsp;&nbsp; public TestSuiteLoader getLoader() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new StandardTestSuiteLoader();<BR>&nbsp;&nbsp;&nbsp; }<BR>但是junit.swingui.TestRunner就很自作聪明了，为了避免每次在点“run”按钮的时候装载运行器本身，就直接使用了基类的方法去获取装载器），这样基类就可以调用自己的getLoader方法来决定要启用那个classloader：</P><P>&nbsp;&nbsp;&nbsp; public TestSuiteLoader getLoader() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (useReloadingTestSuiteLoader())<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new ReloadingTestSuiteLoader();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new StandardTestSuiteLoader();<BR>&nbsp;&nbsp;&nbsp; }</P><P>如果我们用sun的jdk的话，这个方法会返回一个TestCaseClassLoader对象，而这个对象在装载class的时候总是调用creatLoader方法：<BR>&nbsp;&nbsp;&nbsp; protected TestCaseClassLoader createLoader() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new TestCaseClassLoader();<BR>&nbsp;&nbsp;&nbsp; }</P><P>返回的其实是TestCaseClassLoader。这样如果被测试类使用了log4j的话，会造成org.apache.log4j.Appender类被 sun.misc.Launcher$AppClassLoader(也就是sun.misc.Launcher类的嵌入类AppClassLoader)装载一次（在启动test的过程中vm自动装载被引用到的类），然后在运行的时候又被junit.runner.TestCaseClassLoader再装载一次。由两个装载器装载进来的类不管是不是来自同一个.class文件，都会被认为是两个不同的类。因此就造成了上面的错误。<BR>同样的，如果你在自己的代码里面这样装载类：<BR>MyClass myClass = (MyClass)Thread.currentThread().getContextClassLoader().loadClass(mClassName);<BR>也会造成相同的问题并抛出ClassCastException。因为MyClass是在运行测试的过程由junit.runner.TestCaseClassLoader装载的，而Thread.currentThread().getContextClassLoader()却指向的是sun.misc.Launcher$AppClassLoader。</P><P>解决方法<BR>1 java -Dlog4j.ignoreTCL junit.swingui.TestRunner<BR>我猜TCL是ThreadClassLoader的缩写，这个参数的意思大概就是让log4j忽略Thread自己的类装载器（sun.misc.Launcher$AppClassLoader），改而使用当前Class的装载器（junit.runner.TestCaseClassLoader）来装载。但是这个方法只能解决log4j的错误报告（改变了org.apache.log4j.ConsoleAppender的装载方式），但是对我们自己写的代码中的问题却没有作用。</P><P>2 在我们自己的类里面写上一段静态代码：<BR>&nbsp; static{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread.currentThread().setContextClassLoader(MyClassFactory.class.getClassLoader());<BR>&nbsp; }<BR>和方法一类似，这也是在工厂类中用加载了当前lass的装载器（TestCaseClassLoader）来代替Thread的初始化装载器sun.misc.Launcher$AppClassLoader。这个方法可以解决我们自己代码中的问题，并且不会带来影响原来的其他代码。结合第一种方法可以解决上面的两个问题。但是如果你有好几个工厂类，或者你用的其他包里面用了这样的装载方式……那你还可以试试下面的偏门：</P><P>3 注意到BaseTestRunner要进行一个useReloadingTestSuiteLoader()判断才决定返回哪个装载器<BR>public TestSuiteLoader getLoader() {<BR>&nbsp;&nbsp;&nbsp; if (useReloadingTestSuiteLoader())<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new ReloadingTestSuiteLoader();<BR>&nbsp;&nbsp;&nbsp; return new StandardTestSuiteLoader();<BR>}<BR>我们来看看这个判断过程：<BR>protected boolean useReloadingTestSuiteLoader() {<BR>&nbsp;&nbsp;&nbsp; return getPreference("loading").equals("true") &amp;&amp; !inVAJava() &amp;&amp; fLoading;<BR>}<BR>嗯，里面有个inVAJava()是什么玩意儿？<BR>public static boolean inVAJava() {<BR>&nbsp;&nbsp;&nbsp; try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Class.forName("com.ibm.uvm.tools.DebugSupport");<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; catch (Exception e) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; return true;<BR>}<BR>原来它是想判断如果当前使用的是ibm的虚拟机就使用默认装载器，但是判断的条件也忒简单了点，很容易就吧它给蒙过去了：<BR>在当前工程下创建com.ibm.uvm.tools包，在其中创建DebugSupport类：<BR>package com.ibm.uvm.tools;<BR>public class DebugSupport{}<BR>没有错，就这个空白的类，这样就可以把junit.swingui.TestRunner给蒙倒。这样做据说的副作用是，每次点run按钮的时候，都要重起gui环境，但是我没有发现有什么区别。不过要是没有区别，人家又干吗费那么多事呢？不解。</P><P>参考资料</P><P><A href="http://mail-archives.apache.org/mod_mbox/logging-log4j-user/200301.mbox/%3C3E1F1A31.2000605@attbi.com%3E"><U><FONT color=#800080>http://mail-archives.apache.org/mod_mbox/logging-log4j-user/200301.mbox/%3C3E1F1A31.2000605@attbi.com%3E</FONT></U></A></P><BR>[<A href="javascript:StorePage()"><U><FONT color=#0000ff>点击此处收藏本文</FONT></U></A>] </DIV><DIV class=postFoot>发表于 2005年04月29日 10:48 AM </DIV></DIV><P><LINK href="http://blog.csdn.net/emu/Services/Pingback.aspx" rel=pingback><!--<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"xmlns:dc="http://purl.org/dc/elements/1.1/"xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"><rdf:Descriptionrdf:about="http://blog.csdn.net/emu/archive/2005/04/29/367679.aspx"dc:identifier="http://blog.csdn.net/emu/archive/2005/04/29/367679.aspx"dc:title="自作聪明的junit.swingui.TestRunner"trackback:ping="http://blog.csdn.net/emu/services/trackbacks/367679.aspx" /></rdf:RDF>--><BR></P><DIV id=comments><H3></H3><DIV class=post><DIV class=postTitle>maggie&nbsp;发表于<SPAN>2005-04-29 11:43 AM</SPAN>&nbsp;&nbsp;</DIV><DIV class=postText>果然看不不懂</DIV></DIV><BR><DIV class=post><DIV class=postTitle><A href="http://blog.csdn.net/emu/" target=_blank><U><FONT color=#800080>emu</FONT></U></A>&nbsp;发表于<SPAN>2005-04-29 6:02 PM</SPAN>&nbsp;&nbsp;</DIV><DIV class=postText>你将来长大了就懂了呵呵</DIV></DIV><BR><DIV class=post><DIV class=postTitle>Pingback/Trackback&nbsp;发表于<SPAN>2005-04-29 6:36 PM</SPAN>&nbsp;&nbsp;</DIV><DIV class=postText>试一试</DIV></DIV><BR><DIV class=post><DIV class=postTitle>linux_china&nbsp;发表于<SPAN>2005-05-12 8:13 PM</SPAN>&nbsp;&nbsp;</DIV><DIV class=postText>please modify excluded.properties in junit.jar,and execude some package will be all right.</DIV></DIV></DIV></PRE><img src ="http://www.blogjava.net/emu/aggbug/4754.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/emu/" target="_blank">emu</a> 2005-05-18 14:08 <a href="http://www.blogjava.net/emu/articles/4754.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>