2008年10月28日 星期二

關於權限控制策略的討論

表結構為user--role--menu。
我要實現的功能是:
1.對系統菜單(頁面)的控制,不可訪問的不顯示;
2.對頁面上的具體操作進行控制,如該用戶對該模塊只有查詢權限,不能修改、刪除。
對於第一點,我可以做到。根據當前登錄的用戶,獲取他擁有的角色,進而獲取他可訪問的全部模塊列表,顯示這些可以訪問的模塊。
對 於第二點,我有個想法,在數據庫中,對模塊的訪問設定級別,1-全部操作;2-只能查詢。這樣的話,顯示頁面時,判斷該用戶對該頁面的訪問級別,若為1, 顯示“增加、刪除”按鈕,若為2,只顯示查詢按鈕。但這樣的話,實際上時把權限控制的代碼寫到了各個頁面,很不雅觀。


對合同管理模塊,我們在公司權限系統中新增了五種角色:合同錄入人、合同查閱人、合同審核人、合同管理員、超級管理員。在合同管理權 限設置中,對員工賦角色,一個角色可能有多個員工,而一個員工可能也有多個角色。然後設置一員工所能夠管理的部門。基本業務邏輯是,誰(員工)對何部門有 何權限(5種角色)。

上述系統涉及到以下幾張表:

1.角色表。 (ID),角色名,描述。
2.用戶表。 (HrID),描述。
3.部門表。 (DeptID),描述。
4.部門-角色-用戶表。 (ID,HrID,DeptID)。

在表4中,完成上述的業務邏輯,也即“誰對何部門有何權限”。這裡的權限是用角色名來標識,比如合同錄入人這個角色,擁有合同錄入的 權限,合同查閱人這個角色,擁有查閱合同的權限。而部門的樹形結構,則是依賴於DeptID。 DeptID由12位數字構成,每兩個1組,由左至右分別對應部門的級別。比如010500000000代表**公司**部門。實際使用時,通過like ‘01%’,like ’0105%‘判斷員工所管理的部門(個人認為,這里相當Cool)。

我們用公司現有的權限系統來管理角色,增加部門-角色-用戶映射表來管理權限,在前台頁面中完成用戶的授權。這種設計結構相對清 楚,但在應對高靈活的情況是,可能力不從心,比如要增加合同確認這一功能,那麼必須在增加合同確認人角色等。所以有以下一些考慮。

1.將角色和角色所具有的操作分離,常見的操作包括Insert(新增),Select(查看),Update(更新),Delete等操作。用一個素數 對應這四個操作,分別為2,3,5,7。將角色和操作的對應關係放入數據庫。比如合同錄入人所擁有的操作對應2,而合同查閱人對應3,合同審核人對應3, 合同管理員對應3*7。在Unix系統中,採用1,2,4對應讀、寫、執行三種操作,將這三種標識碼相加,對應組合操作。
2.角色體現在業務流程上,如審核人只有讀的操作,流程上可以對一個合同審核通過,或者是駁回重填。

在《關於權限系統的探討》一文中,還考慮了“限定內容列表”,以區分能夠被操作的表、字段。由(ID)、名稱、描述構成。比如在我們的這個合同管理系統中,可能就是A用戶可以修改數據庫裡的這幾個表,而B用戶只能修改數據庫裡的那幾個表。

Simple is beautiful! 發覺自己思路開始混亂。這樣設計複雜,同時和原有的系統並不兼容,純屬遐想。

http://topic.csdn.net/t/20030413/09/1653829.html

2008年10月24日 星期五

FFT - Fast Fourier Transform Program (Algorithm)

Copy From http://people.csail.mit.edu/hammond/teaching/hide/fft/

public class fft
{
private double[] YREAL;
private double[] YIMAG;
private double[] XREAL;
private double[] AFFT;
private double[] BFFT;
private double[] COSX;
private double[] SINX;
private int[] POW;

fft(String[] datapoints)
{

try
{

// put the given values in to an array
YREAL = makeRealArray(datapoints);
YIMAG = makeImaginaryArray(datapoints);

// make array of x values
XREAL = makeXArray(Integer.parseInt(datapoints[0]));

// the given values function are starting values for
// Fourier Results
AFFT = YREAL;
BFFT = YIMAG;

// make cos(x) array and sin(x) array
COSX = makeCosXArray(XREAL);
SINX = makeSinXArray(XREAL);

// generate power array
POW = makePowerArray(Integer.parseInt(datapoints[0]));

// actual fft calculation done here
calculateABValues(Integer.parseInt(datapoints[0]));

// unscramble the vertices according to POW values
unscramble();

}
catch (Exception e)
{
System.out.println("Exception " + e.toString());
e.printStackTrace();
System.exit(-1);
}

for(int i = 0; i < realarray =" new" i =" 0;" imaginaryarray =" new" i="0;" xarray =" new" i =" 0;" cosxarray =" new" i =" 0;" sinxarray =" new" i =" 0;" powerarray =" new" i =" 0;" totalnumberofruns =" (int)" i =" 1;" j =" 1;" j =" 0;" i =" 0;" stage =" 1;" numberofsets =" 1;" cyclelength =" NumberOfData/2;" k =" 0;" tempa =" AFFT;" tempb =" BFFT;" setnumber =" 1;" i =" 0;" j =" (i" l =" POW[(int)(k/cycleLength)];"> AFFT.length || l > AFFT.length || j+cycleLength > AFFT.length)
System.out.println("HELP");
TEMPA[k] = AFFT[j] + COSX[l]*AFFT[j+cycleLength] - SINX[l]*BFFT[j+cycleLength];
TEMPB[k] = BFFT[j] + COSX[l]*BFFT[j+cycleLength] - SINX[l]*AFFT[j+cycleLength];
k++;
}
}
AFFT = TEMPA;
BFFT = TEMPB;
stage++;
NumberOfSets *= 2;
cycleLength /= 2;
k = 0;
} while (stage <= (int)(Math.log(NumberOfData)/Math.log(2))); } /** * Method: unscramble * Input: none * Output: none * Function: Rearranges the A and B values */ private void unscramble() { double[] tempA = AFFT; double[] tempB = BFFT; for (int i = 0; i < length ="=" data1 =" {" alg =" new" i =" 0;"> Integer.parseInt(args[0]))
printUsage();
} while (true);

// make sure number entered equals the number of
// datapoints
if ((args.length - 1)/2 != Integer.parseInt(args[0]))
printUsage();

}
catch (Exception e){
// probably error in input
printUsage();
}

// all is good.
// start the program (Fast Fourier Transform)
fft alg = new fft(args);

}

private static void printUsage()
{
System.out.println("Fast Fourier Transform");
System.out.println("Returns the fft of a list of data points");
System.out.println("--usage");
System.out.println(" fft number datapoints");
System.out.println();
System.out.println("Number must be the number of datapoints");
System.out.println("Number must be power of 2");
System.out.println("Datapoints must be written as: y1real y1imaginary y2real y2imaginary...");
System.out.println("X values are considered to be standardized on [0,2*pi]");
System.out.println();
System.exit(0);
}
}

2008年10月20日 星期一

Java Focus 簡談

視窗系統一般包含一個桌面GUI若干應用程序GUI。每個GUI都由組件構成,每個組 件都可以獲得focus,獲得focus的組件將獲得之後的鍵盤事件,而任意時刻只有一個組件能獲得focus。這個設計適用在當前所有的視窗系統,而跨 各種系統的JAVA應用,其focus的表現也要遵循這個設計目標。

JAVA的組件分為重量級和輕量級組件,區別在於重量級組件實例的成員peer-對等體,其行為緊密依託本地系統的GUI行為函數庫來進行實現。比如一個 JFRAME,當setvisible時,會依托peer.show進行屏幕繪製行為,該行為會通過本地系統GUI行為函數庫完成;這樣一來,當其被點擊 時,本地系統會依據最初調用本地GUI函數繪製時留下的信息,從而能夠經底層處理後(比如將該鼠標事件附加peer標記信息,同時可能經底層分析需要構造 出一個可能的focus_gain事件,則在操作系統層面登記當前聚焦GUI組件等)準確將底層GUI事件派送給該JVM進程,該事件因而在jvm進程中 的AWT-Windows線程loop獲取到,並通過事件提供的peer標記最終確定目標為重量級組件JFRAME,因此一個source== JFRAME的AWTEvent被構造出來並最終分派給EDT進行後續處理。

事件機制是程序中家喻戶曉的設計模式了。但是,看java的focus實現中對這個機制似乎多少有些不那麼絕對的清晰J。

個 人理解,事件的含義就是某種定義的情況發生了。比如點擊鼠標這個動作可以說觸發了多個事件,如press,release,click等,分別指發生了鼠 標button1按下,放開,完成點擊的情況。 button1按下這個事件比起完成點擊就要更基礎一些,因為完成點擊指的是一個由按下,放開動作序列組合的情況發生了。

那麼對於focus,focus_gained,focus_lost這兩個事件應該是指某組件獲得焦點或失去焦點的情況發生了,反映在機器裡,應該是某種指向當前聚焦組件的全局變量發生了更新。

然而在java awt實現裡,概念混亂出現啦。

如 果awt_windows loop到了focus事件,一,這個事件一定是目標向重量級組件的;二,此時,這個事件對於底層系統的對等組件,focus_gainded是發生了 (底層系統標記當前聚焦組件的全局變量已經更新;底層操作系統沒有mess,總是在真正focus改變後才分發focus事件),然而在java層面,截 至到awt_windows loop到底層focus事件並包裝成FocusEvent放置到EVENT QUEUE時,java層面並沒有更新jvm裡的全局變量。所以我個人認為這個時候就不應該包裝成FocusEvent,至少不應該叫這個名字,應該叫 PrepareFocusEvent,嘿嘿。

澄清事件機制的概念後,回頭看java focus要實現的目標。

1.當用戶從別處聚焦點切換到某文本框時(通過約定好的一些鍵盤鼠標操作,比如鼠標點擊或其他可能的動作),需要更新JAVA全局變量使該文本框成為聚焦組件,而後通知該組件的事件監聽focusgainedlistener。

當 前的難點是,輕量級組件雖可以設計在組件切換操作(如mouse_press)的listener中實現全局變量更新,然而問題是如果輕量級組件的容器是 一個重量級組件,比如一個Panel,而此時它的本地對等組件在本地系統中很可能還沒有獲得焦點。若實現上只是在切換操作監聽中簡單的把java的全局變 量更新了,那系統中就會在此時間出現兩個聚焦組件:一個是底層系統承認的原來的某底層對等組件,一個是java裡認為的現在的jtextfield。在這 個時空裡界面將很難不混亂,你很可能會看到最顯眼的是有一個亮亮的TextField,而那個上面有“已經聚焦”的jtextfield的panel面板 卻灰灰的,而你輸入的一個個字符竟然在灰灰的jtextfield中顯示出來。

2.可以提供某方法供應用調用,比如setfocus指定某組件聚焦。

難 點是根據前面的分析,指定輕量級組件聚焦,則要在處理中要通知其重量級容器對應的本地對等組件聚焦並等到它確實聚焦完成了再更新JAVA的全局變量。若實 現上真就是在setfocus調用中直接調用底層setfocus,問題出現在即使底層系統根據調用通知更新了focus,馬上還會繼續對外設可能的焦點 切換操作響應(可以認為有一個系統進程在處理外設的響應),很有可能別的C應用就在此時再要求focus,於是接著就更新了底層的focus登記;而我們 的setfocus調用卻是在jvm進程的某線程中,顯然這就是個並發的情景,這樣,很有可能我們的對本地對等組件的通知發過去並返回了,那邊底層系統就 馬上切換到了C的某個組件focus,而我們的線程繼續更新JAVA的全局focus變量並隨後發布 focus_gained(target==jcomponent),於是又出現了不一致的場景。難道我們要同步這兩個進程,讓系統進程等待我們的調用 setfocus的線程返回再繼續運行,顯然那樣是不合理的。 (JAVA只能服從OS,不能讓OS服從JAVA。---出自《英雄亂語》J)

鑑於1,2的分析,java focus的實現是setfocus是requestfocus,意思是只是將這個切換焦點的請求登記上但並不實際切換focus;隨後如果重量級組件聚焦了再處理request並徹底完成focus切換。

3.聚焦組件後馬上獲得隨後的鍵盤事件。

難 點是按用戶的實際想法,mouse_press後,馬上就要鍵盤拼寫,鍵盤的輸入應該target到mouse_press的jtextfield。根據 前面的分析,mouse_press響應中requestfocus/setfocus後並沒有意味著切換焦點已經完成。若實現上對於後續的鍵盤事件只是 簡單地根據JAVA的那個全局focus變量target,則這些鍵盤事件將不會target到期待的組件上。

鑑於3的分 析,java focus的實現是requestfocus時,如果這個請求按普遍規則一定通過而只是在等待時機,那麼在處理這個請求返回前就登記一個時間戳,在這個時 間戳之後在下一個requestfocus時間戳之前,EDT逐個取的keyevent都將target到該組件並登記,直到該組件徹底聚焦完成後,馬上 把這些keyevent dispatch。

4.需要支持TAB鍵等焦點遍歷操作。

這一點JAVA有一個遍歷模型,如下:

具體參照http://java.sun.com/javase/6/docs/api/java/awt/doc-files/FocusSpec.html

該要求並沒有難點。實現上只要對keyevent監聽,並根據規則進行合適處理即可。

2008年10月18日 星期六

JAVA 6 讓 Webservice 變簡單

以前的經驗,開發 Webservice 的程式是一件相當麻煩的事情,環境設定、編輯 WSDL、程式佈署等等,即使用 IDE 的工具,都不是三言兩語就可以搞定。

JAVA 6 的出現,拯救了像我這種怕麻煩的人 ...

關鍵在於兩個 annotation: @WebService 和 @WebMethod

參考了 Vivek Pandey's Blog 学习Java6(一) WebServices(3)在tomcat中发布,簡單地整理開發 Webservice 的 Server 端和 Client 端程式的過程,以及如何部屬到 Tomcat:


參考 Vivek 的範例,寫一個加法的 Webservice
Server Side
WebServiceStarter.java

public class WebServiceStarter extends HttpServlet{

private static final long serialVersionUID = 5870534239093709659L;

public WebServiceStarter() {
super();
}

public void destroy() {
super.destroy();
}

public void init() throws ServletException{
System.out.println("\nStarting Calculator Service ......\n");
try{
Endpoint.publish("http://localhost:8088/calculator", new Calculator());
}catch (Exception e){
System.out.println("Caught Exception: " + e.toString());
}
System.out.println("\nCalculator Service is OK!\n");
}
}

在 Endpoint.publish 的部份,原本以為 Port 的設定應該與 Tomcat 的預設值 8080 一樣,但是在啟動 Tomcat 的時候發現會跟 Tomcat 搶 8080 Port,所以我改成 8088。

加法的 Service:Calculator.java

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

public class Calculator{
@WebService(targetNamespace = "http://localhost/sample")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public class Calculator {

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

Deploy
程式編譯好後,在 Tomcat 的 webapps 目錄下建立一個 Service 的目錄,如: sample,其內在建立 WEB-INF 目錄,在此目錄裡建立 web.xml:

web.xml


web.xml


WebServiceStarter
WebServiceStarter
1




在 WEB-INF 裡面在建立 classes 的目錄,把編譯好的 Calculator.class 和 WebServiceStarter.class 複製到裡面,重新啟動 Tomcat。檢視 Tomcat 的 log :

catalina.out

Jun 29, 2007 5:16:16 PM org.apache.catalina.core.AprLifecycleListener init
INFO: The Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: /usr/lib/jvm/java-6-sun-1.6.0.00/jre/lib/i386/client:/usr/lib/jvm/java-6-sun-1.6.0.00/jre/lib/i386:/usr/lib/jvm/java-6-sun-1.6.0.00/jre/../lib/i386:/usr/java/packages/lib/i386:/lib:/usr/lib
Jun 29, 2007 5:16:16 PM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
Jun 29, 2007 5:16:16 PM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 875 ms
Jun 29, 2007 5:16:16 PM org.apache.catalina.core.StandardService start
INFO: Starting service Catalina
Jun 29, 2007 5:16:16 PM org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.13

Starting Calculator Service ......


Calculator Service is OK!

Jun 29, 2007 5:16:18 PM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
Jun 29, 2007 5:16:18 PM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
Jun 29, 2007 5:16:18 PM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/43 config=null
Jun 29, 2007 5:16:18 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 2077 ms

這樣 Server 端就 OK 了!

Java Client
先產生需要的 class: wsimport -keep
http://localhost:8088/calculator?wsdl

CalculatorApp.java

class CalculatorApp {
public static void main(String args[]){
/**
* Instantiate the generated Service
*/
CalculatorService service = new CalculatorService();

/**
* Get the port using port getter method generated in CaculatorService
*/
Calculator calculatorProxy = service.getCalculatorPort();

/**
* Invoke the remote method
*/
int result = calculatorProxy.add(10, 20);
System.out.println("Sum of 10+20 = "+result);
}
}

編譯,執行,結果應該是:Sum of 10+20 = 30

Flex Client
用 Flex 做一個比較好用的 GUI:

測試網址:http://ir.tmu.edu.tw/calc/

有沒有很簡單呢,呵呵!

2008年10月17日 星期五

50 Excellent AJAX Tutorials

一篇文章講 50 Excellent AJAX Tutorials,這篇文章列出了 50 則關於 AJAX 的教學文章,可以幫助剛要上手 AJAX 的人學習之用,每篇文章之間談到的技術領域也很廣,有 PHP, ASP.NET, Java, JavaScript, jQuery, MooTools, ... 等等,與許多各種不同的 AJAX 應用,我覺得蠻不錯的,有興趣的人可以去看看,挑一些自己有興趣的主題看看別人的文章吧(順便練練英文)。

其中有幾則跟 ASP.NET 有關的我列出來:

  1. How to Call Server-Side Function from Client-Side Code Using PageMethods in ASP.NET AJAX

    這篇文章教你如何透過 JavaScript 直接呼叫 Server 端的程式!

  2. Use jQuery and ASP.NET AJAX to Build a Client-Side Repeater

    這也是個蠻不錯的開發技巧,徹底將 Client 端與 Server 端分離,頁面的顯示效率將大幅的提升,另一方面也減輕 Server 端程式的運算負擔。

  3. ASP.NET AJAX Calendar Extender

    這篇介紹 AJAX Control Toolkit 中 Calendar Extender 的 7 種開發技巧。

  4. JavaScript Error Publishing using ASP.NET AJAX

    這 篇文章更酷了,他介紹如何讓你在 Client 發生 JavaScript Error 時如何自動將完整的錯誤訊息送回 Server 端,並讓你可以從 Server 端進行錯誤的儲存以利後續分析與追蹤。這一點通常也是 AJAX 網站最常犯的問題,因為 JavaScript 出錯時通常開發人員都不曉得,都一定要使用者才能通知,若實做此機制將可以進一步掌握 Web 應用程式的品質。

除了這幾篇以外,其他的文章大多也都有完整且詳細的程式碼範例與相關說明,真是個不錯的學習管道。

相關連結

2008年10月16日 星期四

實現簡單的Web容器

一個類似Tomcat的容器,不過只是模擬Web容器的解析過程,只做了簡單的解析HTML。
首先用Java IDE建立一個普通的Java Project,要解析web少不了Request和Response對象,所以在工程(項目)裡建立兩個類:MyHttpServletRequest(對應HttpServletRequest)、MyHttpServletResponse(對應HttpServletResponse),如下:

//
MyHttpServletRequest.java
package com.kalman03.servlet;

import java.io.BufferedReader;
import java.io.IOException;

public class MyHttpServletRequest{
private BufferedReader br;
private String header;
public MyHttpServletRequest(BufferedReader br){
try{
this.br = br;
this.header = br.readLine();// GET /index.html HTTP/1.1
}catch(IOException ex){
ex.printStackTrace();
}
}

public String getURL(){
String [] temp
= header.split(" ");
return temp[1].substring(1);
}
}





//MyHttpServletResponse.java
package com.kalman03.servlet;

import java.io.PrintWriter;

public class MyHttpServletResponse{
private PrintWriter out;
public MyHttpServletResponse(PrintWriter out){
this.out = out;
out.println(
"HTTP/1.1 200 OK");
}

public PrintWriter getWriter(){
return out;
}
}


構造一個抽像類MyHttpServlet:
//MyHttpServlet.java
package com.kalman03.servlet;

public abstract class MyHttpServlet{
public abstract void doGet(MyHttpServletRequest request,MyHttpServletResponse response)throws Exception;
public abstract void doPost(MyHttpServletRequest request,MyHttpServletResponse response)throws Exception;
}


用MyServlet類實現上面MyHttpServlet抽像類:
//MyServlet.java
package com.kalman03.servlet.ext;

import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.File;
import java.io.FileReader;
import com.kalman03.servlet.MyHttpServlet;
import com.kalman03.servlet.MyHttpServletRequest;
import com.kalman03.servlet.MyHttpServletResponse;

//该类继承MyHttpServlet类,实现其中的doGet(),doPost()方法
public class MyServlet extends MyHttpServlet {
private PrintWriter out;
private String url;

public void doGet(MyHttpServletRequest request,
MyHttpServletResponse response)
throws Exception {
this.out = response.getWriter();
url
= request.getURL();
if (url.equals("")) {
url
= "index.html";
}
File file
= new File(url);
FileReader fr
= new FileReader(file);
BufferedReader brR
= new BufferedReader(fr);
String temp;
while ((temp = brR.readLine()) != null) {
out.println(temp);
}
out.close();
brR.close();
fr.close();
}

public void doPost(MyHttpServletRequest request,
MyHttpServletResponse response)
throws Exception {
this.doGet(request, response);
}
}



跑一個線程監聽類MyWebService:
//MyWebService.java
package com.kalman03.servlet;

import java.net.ServerSocket;
import java.net.Socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class MyWebService {
public MyWebService(MyHttpServlet ms) {
try {
ServerSocket ss
= new ServerSocket(2008);
while (true) {
Socket s
= ss.accept();
new myRunnable(s, ms).start();
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}



//線程監聽
class myRunnable extends Thread {
private Socket s;
private MyHttpServlet ms;
public myRunnable(Socket s, MyHttpServlet ms) {
this.s = s;
this.ms = ms;
}

public void run() {
try {
BufferedReader br
= new BufferedReader(new InputStreamReader(s.
getInputStream()));
PrintWriter out
= new PrintWriter(s.getOutputStream(), true);
MyHttpServletRequest req
= new MyHttpServletRequest(br);
MyHttpServletResponse rep
= new MyHttpServletResponse(out);
ms.doGet(req, rep);
br.close();
out.close();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}


至此大工基本告成,再來一個main方法(MainClass類)就OK了:
//MainClass.java
package com.kalman03.test;

import com.kalman03.servlet.MyWebService;
import com.kalman03.servlet.ext.MyServlet;

public class MainClass {
public static void main (String[] args) {
new MyWebService(new MyServlet());
}
}

運行,OK!一個web容器已經啟動,當你在剛建的工程(項目)目錄下丟一個HTML(比如welcome.html),在瀏覽器裡輸入http://127.0.0.1:2008/welcome.html,即可解析welcome.html。

補充一下,這裡設置的默認端口為2008,當沒有指定具體頁面時候,尋找的是index.html

2008年10月13日 星期一

How to sort objects in Java: Comparator

以下是實踐Java排序的一些心得。

可排序的物件(例如Bean)需實作java.lang.Comparable interface,這個interface僅包含了單一方法 - compareTo,compareTo定義了該物件與其他同類型物件該如何排序的規則

compareTo的規則是:透過回傳值int來表示排序的大小,回傳值為負數表示本身物件要排序的屬性,比被比較的物件的該屬性,在邏輯上的地位較小,若為正則相反。回傳值若為0則表示兩物件相等


某些物件本身已經實作了compareTo這個method,像是String、File、Date和Time等物件


若 要排序多個屬性,可建立一個物件實作java.util.Comparator介面,Comparator僅包含compare一個方法,參數型態是 Object。方法內定義了兩個相同物件該如何排序的規則。實作時要經過轉型(有可能造成ClassCastException)。回傳值型態一樣為 int,若回傳值為正數,則obj1的排序在obj2之後,若回傳值為負數,則obj1的排序在obj2之前,若回傳值為0,則兩者相等。規則同 compareTo


同類型物件才能被排序(也才有意義)


實際執行排序的方法包括Arrays.sort和Collnections.sort,兩者都是static method,將該物件陣列及實作Comparator介面的物件傳入後,即可排序(不用回傳值)

==
方法
1

public class FileObject implements java.lang.Comparable {

private String filename;

private Long filesize;

public FileObject() { }

public String getFilename() {

return this.filename;

}

public Long getFilesize() {

return this.filesize;

}

public void setFilename(String name) {

this.filename = name;

}

public void setFilesize(Long size) {

this.filesize = size;

}

public int compareTo(Object obj) throws ClassCastException {

if (!(obj instanceof Example1))

throws new ClassCastException("Error msg here.");

// 先做轉型

FileObject fileobj = (FileObject) obj;

// 使用String內建的規則

return this.filename.compareTo(fileobj.getFilename());

}

}

用法

FileObject[] fileObj = new FileObject[10];

Arrays.sort(fileObj);

方法2 – 實作java.util.Comparator的方式

public class FilesizeComparator implements Comparator {

public int compare(Object obj1, Object obj2) {

File file1 = (File) obj1;

File file2 = (File) obj2;

// 這裡放置要比較的邏輯,不過回傳值要是int,要注意int溢位的問題

if (file1.getFilesize() > file2.getFilesize())

return 1;

if (file1.getFilesize() <>

return -1;

if (file1.getFilesize() = file2.getFilesize())

return 0;

}

}

用法

FileObjectp[] fileObj = new FileObject[10];

Arrays.sort(fileObj, new FilesizeComparator());

2008年10月11日 星期六

Java文件路徑問題

1.如何獲得當前文件路徑

常用:

字符串類型:System.getProperty("user.dir");

綜合:

public class Test {
public static void main(String[] args) throws Exception {
System.out.println(
Thread.currentThread().getContextClassLoader().getResource(""));

System.out.println(Test.class.getClassLoader().getResource(""));
System.out.println(ClassLoader.getSystemResource(""));
System.out.println(Test.class.getResource(""));
System.out.println(Test.class.getResource("/"));
System.out.println(new File("").getAbsolutePath());
System.out.println(System.getProperty("user.dir"));
}
}

2.Web服務中

(1).Weblogic

WebApplication的系統文件根目錄是你的weblogic安裝所在根目錄。
例如:如果你的weblogic安裝在c:\bea\weblogic700.....
那麼,你的文件根路徑就是c:\.
所以,有兩種方式能夠讓你訪問你的服務器端的文件:
a.使用絕對路徑:
比如將你的參數文件放在c:\yourconfig\yourconf.properties,
直接使用new FileInputStream("yourconfig/yourconf.properties");
b.使用相對路徑:
相對路徑的根目錄就是你的webapplication的根路徑,即WEB-INF的上一級目錄,將你的參數文件放在yourwebapp\yourconfig\yourconf.properties,
這樣使用:
new FileInputStream("./yourconfig/yourconf.properties");
這兩種方式均可,自己選擇。

(2).Tomcat

在類中輸出System.getProperty("user.dir");顯示的是%Tomcat_Home%/bin

(3).Resin

不是你的JSP放的相對路徑,是JSP引擎執行這個JSP編譯成SERVLET
的路徑為根.比如用新建文件法測試File f = new File("a.htm");
這個a.htm在resin的安裝目錄下

(4).如何讀相對路徑哪?

在Java文件中getResource或getResourceAsStream均可

例:getClass().getResourceAsStream(filePath);//filePath可以是"/filename",這裡的/代表web發布根路徑下WEB-INF/classes

(5).獲得文件真實路徑

string file_real_path=request.getRealPath("mypath/filename");

通常使用request.getRealPath("/");

3.文件操作的類

import java.io.*;
import java.net.*;
import java.util.*;
//import javax.swing.filechooser.*;
//import org.jr.swing.filter.*;

/**
*此類中封裝一些常用的文件操作。
*所有方法都是靜態方法,不需要生成此類的實例,
*為避免生成此類的實例,構造方法被申明為private類型的。
* @since 0.1
*/

public class FileUtil {
/**
*私有構造方法,防止類的實例化,因為工具類不需要實例化。
*/
private FileUtil() {

}

/**
*修改文件的最後訪問時間。
*如果文件不存在則創建該文件。
* 目前這個方法的行為方式還不穩定,主要是方法有些信息輸出,這些信息輸出是否保留還在考

慮中。

* @param file需要修改最後訪問時間的文件。
* @since 0.1
*/
public static void touch(File file) {
long currentTime = System.currentTimeMillis();
if (!file.exists()) {
System.err.println("file not found:" + file.getName());
System.err.println("Create a new file:" + file.getName());
try {
if (file.createNewFile()) {
// System.out.println("Succeeded!");
}
else {
// System.err.println("Create file failed!");
}
}
catch (IOException e) {
// System.err.println("Create file failed!");
e.printStackTrace();
}
}
boolean result = file.setLastModified(currentTime);
if (!result) {
// System.err.println("touch failed: " + file.getName());
}
}

/**
*修改文件的最後訪問時間。
*如果文件不存在則創建該文件。
* 目前這個方法的行為方式還不穩定,主要是方法有些信息輸出,這些信息輸出是否保留還在考

慮中。

* @param fileName需要修改最後訪問時間的文件的文件名。
* @since 0.1
*/
public static void touch(String fileName) {
File file = new File(fileName);
touch(file);
}

/**
*修改文件的最後訪問時間。
*如果文件不存在則創建該文件。
* 目前這個方法的行為方式還不穩定,主要是方法有些信息輸出,這些信息輸出是否保留還在考

慮中。

* @param files需要修改最後訪問時間的文件數組。
* @since 0.1
*/
public static void touch(File[] files) {
for (int i = 0; i <>目前這個方法的行為方式還不穩定,主要是方法有些信息輸出,這些信息輸出是否保留還在考

慮中。
* @param fileNames需要修改最後訪問時間的文件名數組。
* @since 0.1
*/
public static void touch(String[] fileNames) {
File[] files = new File[fileNames.length];
for (int i = 0; i < files =" new">注意:可能會在返回false的時候創建部分父目錄。
* @param file要創建的目錄
* @return完全創建成功時返回true,否則返回false。
* @since 0.1
*/
public static boolean makeDirectory(File file) {
File parent = file.getParentFile();
if (parent != null) {
return parent.mkdirs();
}
return false;
}

/**
*創建指定的目錄。
*如果指定的目錄的父目錄不存在則創建其目錄書上所有需要的父目錄。
* 注意:可能會在返回false的時候創建部分父目錄。
* @param fileName要創建的目錄的目錄名
* @return完全創建成功時返回true,否則返回false。
* @since 0.1
*/
public static boolean makeDirectory(String fileName) {
File file = new File(fileName);
return makeDirectory(file);
}

/**
*清空指定目錄中的文件。
*這個方法將盡可能刪除所有的文件,但是只要有一個文件沒有被刪除都會返回false。
*另外這個方法不會迭代刪除,即不會刪除子目錄及其內容。
* @param directory要清空的目錄
* @return目錄下的所有文件都被成功刪除時返回true,否則返回false.
* @since 0.1
*/
public static boolean emptyDirectory(File directory) {
boolean result = false;
File[] entries = directory.listFiles();
for (int i = 0; i < result =" false;" dir =" new" dir ="=" entries =" dir.listFiles();" sz =" entries.length;" i =" 0;" fileurl = "file:/" url =" new" file =" new" file =" new" file =" new" point =" fileName.lastIndexOf('.');" length =" fileName.length();" point ="=" point ="=" point =" getPathLsatIndex(fileName);" length =" fileName.length();" point ="=" point ="=" secondpoint =" getPathLsatIndex(fileName," secondpoint ="=" length ="=" point =" getPathLsatIndex(fileName);" length =" fileName.length();" point ="=" point ="=" secondpoint =" getPathLsatIndex(fileName," secondpoint ="=" point =" fileName.indexOf('/');" point ="=" point =" fileName.indexOf('\\');" point =" fileName.indexOf('/'," point ="=" point =" fileName.indexOf('\\'," point =" fileName.lastIndexOf('/');" point ="=" point =" fileName.lastIndexOf('\\');" point =" fileName.lastIndexOf('/'," point ="=" point =" fileName.lastIndexOf('\\'," index =" filename.lastIndexOf(" index =" fileName.indexOf(pathName);">store(output,string)。如果只是單純的讀,根本不存在煩惱的問題。 web層可以通過Thread.currentThread().getContextClassLoader().
getResourceAsStream("xx.properties")獲取;Application可以通過new FileInputStream("xx.properties");直接在classes一級獲取。關鍵是有時我們需要通過web修改配置文件,我們不能將路徑寫死了。經過測試覺得有以下心得:

1.servlet中讀寫。如果運用Struts或者Servlet可以直接在初始化參數中配置,調用時根據servlet的getRealPath("/")獲取真實路徑,再根據String file = this.servlet.getInitParameter("abc");獲取相對的WEB -INF的相對路徑。
例:
InputStream input = Thread.currentThread().getContextClassLoader().
getResourceAsStream("abc.properties");
Properties prop = new Properties();
prop.load(input);
input.close();
OutputStream out = new FileOutputStream(path);
prop.setProperty("abc", “test");
prop.store(out, “–test–");
out.close();

2.直接在jsp中操作,通過jsp內置對象獲取可操作的絕對地址。
例:
// jsp頁面
String path = pageContext.getServletContext().getRealPath("/");
String realPath = path+"/WEB-INF/classes/abc.properties";

//java程序
InputStream in = getClass().getClassLoader().getResourceAsStream("abc.properties"); // abc.properties放在webroot/WEB-INF/classes/目錄下
prop.load(in);
in.close();

OutputStream out = new FileOutputStream(path); // path為通過頁面傳入的路徑
prop.setProperty("abc", “abcccccc");
prop.store(out, “–test–");
out.close();

3.只通過Java程序操作資源文件
InputStream in = new FileInputStream("abc.properties"); //放在classes同級

OutputStream out = new FileOutputStream("abc.properties");關於Java文件路徑問題

2008年10月10日 星期五

Java dnd 拖拽實現分析紀要

既有的Swing組件都內置了拖拽的支持,是怎麼樣支持呢?

首先,在Windows 環境的jvm進程中,一個gui程序將啟動兩個線程:AWT-WINDOWS(AWT)和Event-Dispatch-Thread(EDT)。 AWT-WINDOWS線程不斷從windows操作系統中獲取GUI事件並進行初步的底層處理;其中一些事件會被包裝成高級的AWTEvent置入一個 地方,而EDT線程的處理過程就包括不斷在的適當時機從這個地方獲取這些AWTEvent並進行高級處理。

然後,拖拽的效果就是由以下幾個GUI操作事件及相應程序處理完成的。

1.拖拽開始,拖拽結束。

2.拖拽進入(源組件/目的組件),拖拽經過(源組件/目的組件),拖拽離開(源組件/目的組件),拖拽中鼠標的移動。

3.拖拽操作式樣變換。即隨鍵盤操作,可以指示3種操作式樣:剪切式,複製式,鏈接式。

這 些事件發生後,AWT線程即會獲取到事件通知並處理,底層處理後會包裝交給EDT繼續處理。而處理的程序邏輯一般設計為,對於鼠標圖標,會被設計為隨拖拽 的起始及移動的整個過程中不同事件的發生而發生變化(比如在dragover事件中可能根據當時情況將圖標變為一個叉表示不能拖入);同時,對於拖拽源組 件和目的組件,隨不同的事件通知也會被程序設計為產生不同的變化響應(比如,拖拽結束的事件處理中可能指令目的組件複製源組件的文字內容),從而最終實現 了拖拽的效果。

最後,看一些JRE7中拖拽的實現類。

Swing的JComponent組件將一些功能委託給其成員ComponentUI代理。比如JTextField在構造方法中即會通過 UIManager獲得合適的TextUI實例(UIManager將根據當前look and feel設置獲取)(此後JTextField的paint方法會調用該UI實例的update方法從而完成組件繪畫),並調用該UI實例的 installUI方法(在installUI中則包括給JTextField對象安裝一些監聽器,安裝TransferHandler這個支持拖拽的關 鍵成員,它包含DragHandler,DropHandler兩個內部類,而這兩個內部類是實現拖拽效果的核心邏輯,安裝droptarget)。

Swing將ui類劃分在幾個包中,其中javax.swing.plaf中存放一些接口;javax.swing.plaf.basic中存放對接口的 基本實現,即多種LAF的通用實現;javax.swing.plaf .metal中存放java默認LAF實現;另外還有javax.swing.plaf.multi用來實現多個LAF的綜合效果實 現;javax.swing.plaf.synth用來實現可通過配置xml文件更換顏色等皮膚的實現。

對於拖拽方面,BasicUI在installUI中一般會對組件安裝mouseListener:

editor.addMouseListener(dragListener);

editor.addMouseMotionListener(dragListener);

該 dragListener將監聽發生在組件上的鼠標事件,當發現可能是新啟動的拖拽鼠標動作並且組件dragEnable時,則立刻通知 DragRecognitionSupport單例進行組件拖拽識別。該support單例將辨別鼠標動作本身,確認是組件拖拽開始,再通知組件的 TransferHandler成員對象進行拖拽初始化,即,經其辨別headless環境和action支持後將初始化建立並委託調用 TransferHandler.SwingDragGestureRecognizer的全局單例(成員包括全局單例dragsource及 draghandler對象),該實例註冊拖拽識別listener及設置sourceAction,最終將通知 TransferHandler.DragHandler對象的dragGestureRecognized。在該方法中,將創建 transferable及初始化autoscroll;並通過dragSource全局單例完成創建DragContext,並獲取及初始化 DragContextPeer全局單例(給該單例註冊上該component作為拖拽trigger,以供native code可以在處理底層事件時,可以通過x,y判定是否contains,從而縮小事件處理範圍),並通知DragContextPeer單例拖拽開始, 而DragContextPeer單例則會調用底層native code進行進一步的處理。

此後AWT線程將通過windows- api獲取到系統底層的各種拖拽事件並進行底層處理,處理過程將會隨時引用DragContextPeer單例(處理邏輯包括根據trigger過濾), 並最終通知該單例合適的事件通知。 Peer會將這些事件封裝成合適的DragEvent並提交給EDT處理,提交後將促使AWT線程模擬等待EDT處理完該事件。 EDT的處理邏輯是將事件交給拖拽開始時給組件創建的DragContext處理,而該context對象的處理則會調用其dragHandler成員的 對應方法進行事件處理,以及給dragSource單例相應的監聽通知,最後updateCurrentCursor等,最後EDT返回到 AWT,peer處理返回給native code繼續處理。

當拖拽開始後,鼠標圖標在一個swing組件上游晃時,首先 windows會對其頂層容器(如jwindows)視作拖拽源,經native code過濾後,如果該swing組件是此次拖拽trigger(源),則DragContextPeer能得到dragover的通知,進而進行後續處 理;同時從另一個方面,這個也被視作一個拖拽目的,即AWT線程還會對每次拖拽啟動建立一個DropContextPeer (為將來支持並發),並調用該peer的handle事件方法,該方法會將此底層事件包裝後提交給EDT並促使AWT等待;EDT處理該事件時將 track到該事件發生時的頂層組件,以及事件發生的坐標位置,由頂層容器組件的LightweightDispatcher進行初步處理。這個處理將由 該事件產生新事件,其event source將從container(比如JFRAME)變為精確的jcomponent(比如JTextField),同時對於 eventId=dragover的事件,有可能根據具體情況再增加dragExit,dragEnter兩個事件(比如對jframe窗體是 dragover,但鼠標實際是從一個jtextfield到另外一個jtextfield),這些精確的事件的處理會再次回到 DropContextPeer中得到對應的process。這時的process會處理本身的一些成員數據(當前context,當前 droptarget等),再將事件委託給source jcomponent的droptarget進行處理(如果已安裝),此時的處理將是傳遞給對應組件的drophandler進行處理,同時會通知 droptarget的註冊監聽,最後initializeAutoscrolling等。 drophandler在處理過程中對event可以進行accept或reject,這兩個動作會再去調用dropcontext進行處理,並最終轉到 peer處理成員數據。最後edt返回到AWT,peer handle處理結束返回native code當前drop action值,native code繼續處理。

通過以上的分析,如果需要定制swing組件的拖拽邏輯,一個比較基礎的入口是 transferhandler;因為所有事件的處理都將經過其兩個內部類邏輯處理(DragHandler和DropHandler);而swing包 中的TransferHandler的具體實現,是這兩個內部類的方法都把一些控制劃分給了componet的一些屬性設置或 TransferHandler本身的回調方法,所以只需對組件設置合適的屬性,或繼承並override TransferHandler合適的方法並給此swing組件重新setTransferHandler,即可以定制新的邏輯。如果需要更深層次的定 制,則需要細緻考慮上述分析,選擇合適的定制點。