程式碼高𠅙

2011/09/06

用 Java Annotation 簡化命令列參數處理

撰寫 Java 命令列程式,免不了要處理命令列參數。常見的作法是跑個 for loop, 裡面用 if ... else 去判斷要執行的選項。這樣的作法當然可以達到目的,不過比較不具有程式的美感。

其實在 Web 上,同樣的問題也存在,就是怎麼把 URL 對應到特定的 servlet, action 或 controller 的方法上。這不管是在 Spring Framework 3 Web MVC, Servlet 3 或是 Jessey 中,都有一套近似的解法,就是使用 @Annotation。

OK, 那我們何不如法泡製,也用 @Annotation 來處理命令列參數呢? 我設計的命令列規範如下:
  • 將命令列參數區分成「選項」及「選項參數」。選項如 -start, -stop;選項參數如 -start localhost 18080 中,未加 dash 符號的字串,即紅字部分。
  • 一個命令列可以接受多個選項及多個選項參數。
  • 支援將選項綁定到主程式裡面的方法,以下我稱之為「選項常式」。
  • 支援將選項參數綁定到選項常式的參數上。
  • 支援自動列出所有選項及其說明。
  • 自動檢查選項參數是否正確。 
  • 支選整數型態的選項參數。
基於這個規範,我設計出的這組 Command line API,可以透過類似以下方式使用:

package com.isong.cmdexe;

/**
 * Usage example:<pre>
 * java -jar ds.jar -start localhost 2011
 * java -jar ds.jar -start localhost 2011 -sm
 * java -jar ds.jar -stop
 * </pre>
 * @author edwardsayer
 */
public class DummyServer {
    /**
     * @param args
     */
    public static void main(String[] argv) {
        new CmdExecutor(new DummyServer()).exec(argv);
       
    }
   
    // execute: java -jar ds.jar -start localhost 2011
    @CommandArg(value = "-start ip port", desc = "start the server instance.")
    public void doStart(String ip, int port){
        System.out.println("Starting server at " + ip + ":" + port + "...");
    }
   
    // execute: java -jar ds.jar -sm 
    @CommandArg(value = "-sm", desc = "start session monitor.")
    public void startSessionMonitor(){
        System.out.println("Starting session service...");
    }
   
    // execute: java -jar ds.jar -stop 
    @CommandArg(value = "-stop", desc = "stop the server instance.")
    public void doStop(){
        System.out.println("Stoping...");
    }
}

從以上的程式碼片段,可以知道這組 Command line API 提供了一個 CmdExecutor 類別, 以及一個 @CommandArg annotation。

@CommandArg 會在主程式的選項常式上,加上標注訊息,包括選項名稱,以及選項描述。

而CmdExecutor 類別則依據 @CommandArg 所提供的資訊,綁定使用者輸入的參數。另外,若使用者未輸入任何參數,CmdExecutor 則顯示參數提示訊息,這些訊息,其實是我們透過 @CommandArg 加諸在每個選項常式上的訊息。範例如下:

Available options:
-start ip port
    start the server instance.
-sm
    start session monitor.
-stop
    stop the server instance.

接下來,就來了解 CmdExecutor 及 @CommandArg 的實作方式。先看看 @CommandArg 的程式碼:

@Retention(RetentionPolicy.RUNTIME)
public @interface CommandArg {
    String value();
    String desc() default "";
}

可以看到 @CommandArg 包含兩個屬性。value 用來設定選項名稱,而 desc 用來設定選項描述。

CmdExecutor 較為冗長,程式碼如下:

public class CmdExecutor {
    private Map cmdMap;
    private StringBuffer cmdDocs;
    private Object target;
    
    public CmdExecutor(Object target){
        this.target = target;
    }
    
    public int exec(String[] args){
        System.out.println("args = " + Arrays.asList(args));
        initCommands();
        if (args.length == 0) {
            showCommands();
            return 0;
        }
        
        for (int i = 0; i < args.length; i++) {
            if (args[i].startsWith("-")) {
                String option = args[i];
                Method method = cmdMap.get(option);
                if (method == null) {
                    System.out.println("Unknown options: " + option);
                    showCommands();
                    return 1;
                }
                try {
                    Class[] types = method.getParameterTypes();
                    Object[] paras = new Object[types.length];
                    for (int j = 0; j < types.length; j++) {
                        if ( ++i >= args.length || args[i].startsWith("-")) {
                            System.out.println("Invalid option parameter count: " + option);
                            showCommands();
                            return 1;
                        }
                        if(types[j].isAssignableFrom(String.class))
                            paras[j] = args[i];
                        else if(types[j].isAssignableFrom(int.class) 
                                || types[j].isAssignableFrom(Integer.class))
                            paras[j] = Integer.valueOf(args[i]);
                        else
                            throw new IllegalArgumentException("Invalid command parameter type: " + method.getName());
                    }
                    method.invoke(target, paras);
                } catch (Exception e) {
                    e.printStackTrace();
                } 
            }
        }
        return 0;
    }
    
    private void initCommands(){
        if (cmdDocs != null)
            return;
        
        cmdDocs = new StringBuffer("Available options:\n");
        cmdMap = new HashMap();
        Method[] methods = target.getClass().getMethods();
        for (Method method: methods) {
            CommandArg argCmd = method.getAnnotation(CommandArg.class);
            if (argCmd != null) {
                cmdDocs.append(argCmd.value() + "\n    " + argCmd.desc() + "\n");
                cmdMap.put(argCmd.value().split(" ")[0], method);
            }
        }
    }
    
    private void showCommands(){
        System.out.println(cmdDocs);
    }
}

以上主要程式邏輯放在 exec 這個方法上,它會透過呼叫 initCommands 方法,建立所有選項常式之描述 (cmdDocs) 及對應 (cmdMap)。完成後,就是實際去解析命令列參數。如果沒有任何參數,就透過 showCommands 顯示說明。如果有,就進行選項與主程式方法的對應。
 

2010/02/28

用 AutoHotkey 啟用雲端字典及翻譯

Google 字典一直是我相當喜愛使用的網路服務,簡潔的版面及清晰的解釋及例句,都是我喜歡它的原因…硬要說它有什麼缺點的話,就是沒有看到日文的支援。固然透過 Firefox 或 Chrome,我們可以很容易的瀏覽器中啟用翻譯服務,但若是想要在其他應用程式中也使用到 Google 字典,就必須借助其他方法了。

AutoHotKey 是一個 Windows 下的桌面工具,可以讓我們透過自訂的快速鍵,啟動特定的應用程式、開啟文件、或指定的 URL 等。我們可以利用 AutoHotKey 的功能,在桌面上一鍵啟動 Google 字典及翻譯。

  1. 下載 AutoHotKey
  2. 執行安裝
  3. 安裝完成後,執行 AutoHotKey
  4. 在 Windows 的 System Tray 中,在 AutoHotKey 的 [H] 按鈕上按右鍵,帶出右鍵功能表,選擇「Edit This Script」
  5. 此時 AutoHotKey 預設會以 Windows 內建的「記事本」(Notepad) 開啟 AutoHotKey.ahk,供我們編輯
  6. 接下來就得編輯 AutoHotKey 的指定碼了,所幸相當簡單。其 Help 中有詳細的說明,我把常用的網頁服務設定如下的代碼區塊。
  7. 網友們可複製以下程式碼,貼到 AutoHotKey.ahk 檔案最後,然後存檔,關閉記事本。
  8. 使用 AutoHotKey 的右鍵功能表,執行「Reload This Script」。

至此大功告成,以後在任何應用程式中,只要先選定 (反白) 要查詢的文字,然後按下 Win + X 組合鍵,就可將選定文字帶至 Google 字典中查詢;此外,你也可以使用 Win + S 啟用 Google 搜尋,或使用 Win + Z 啟用 Google 翻譯。

另外,如果你想要讓 Windows 啟動時就自動執行 AutoHotKey.ahk,可以為 AutoHotKey.ahk 建立一個捷徑,並將捷徑放置在開始功能表裡面的「啟動」目錄即可。

Ahk 代碼:

;; 定義按 Win + S 鍵,啟用 Google 搜尋
#s::
clipboard = ; Empty the clipboard
Send, ^c
ClipWait, 2
Run http://www.google.com.tw/search?hl=zh-TW&q=%clipboard%
return

;; 定義按 Win + X 鍵,啟用 Google 字典
#x::
clipboard = ; Empty the clipboard
Send, ^c
ClipWait, 2
Run http://www.google.com.tw/dictionary?aq=f&oq=breed&langpair=en|zh-TW&hl=zh-TW&q=%clipboard%
return

;; 定義按 Win + Z 鍵,啟用 Google 翻譯
#z::
clipboard = ; Empty the clipboard
Send, ^c
ClipWait, 2
Run http://translate.google.com.tw/translate_t#auto|zh-TW|%clipboard%
return

Technorati : , , , ,

2009/09/15

WinForm: 如何判斷表單是否開啟在 Visual Studio 的 Designer 中

最近工作上用到比較多微軟的開發工具,像是 Visual Studio, Reporting Servoces, ASP.NET 等。

我們採用了一套 UI Framework,可以在 ASP.NET 上,以 WinForm 的程式設計模式來開發 Web 表單。在表單開發上,可完全免除自行撰寫 Client 端 JavaScript 的需要。簡言之,這種開發模式具有 WinForm 的優點,相對的,開發 WinForm 時所需要面對的問題,這裡同樣要面對。

例如,在開發 WinForm 表單時,我們常會在元件的 Load 事件載入資料來源,進行資料綁定。在執行環境這樣的邏輯沒有問題,但在開發環境,以 Visual Studio 的 Designer 開啟元件時,就可能會遇到無法讀取設定檔、資料連結錯誤等等問題,而造成 Designer 無法順利開啟表單。

我查詢了一下,在 MSDN Windows Developer Center 上也有人詢問這個問題,其中有個開發者已經給了一個可用的 solution,但因為他的描述過於複雜,且要求改寫基底類別,所以我擷取了他的解法,加以簡化。

首先,我們撰寫一個 GuiUtil 類別,其中包括下列方法:

public static bool InDesignMode() 
{
    try 
    { 
        return System.Diagnostics.Process.GetCurrentProcess().ProcessName == "devenv";
    }
    catch
    {
        return false;
    }
}

如此一來,我們就可以採用以下的程式碼,在 Client 端程式的 Load 事件中進行呼叫,而不會產生設計畫面出不來的狀況了。

if (!GuiUtil.InDesignMode())
{
        InitDataBinding();
}

-- 完 --

2009/07/07

有趣的 MIS 大大

剛剛 MIS 大大寄來一封停機公告,重點在那張圖:

各位辛苦的系統開發大大們,

這件事情已經拖了一個多月了,實在不一在拖下去了…

知道各位都相當的忙碌,不過一個多月的時間應該也足夠才對。

剛剛看過黃曆,7/18 是個好日子,我們會設定在 7/18 子時系統自動關機,之後就不再提供服務了!

如果各位還有資料沒有轉到新的測試主機 xx.xxx.xx.xxx 的,請利用這兩週趕快轉移。

感謝您的配合!!

yellow calendar.png

2009/07/04

Firefox 小技巧--將收藏庫開啟於分頁標籤中

Open Firefox Library in a Tab

不曉得有沒有人曾想過,把 Firefox 內的收藏庫 (Library, 管理書籤跟歷史的使用界面),開啟於分頁標籤中,而不是獨立視窗中。

如果你也曾想這麼做,那就這麼做吧 -- 在網址列中輸入以下 URL:

chrome://browser/content/places/places.xul

噹噹…

現在收藏庫便開啟於分頁標籤之中了:

Firefox Library in a Tab

Technorati : , , , ,

2009/06/05

JavaFX 與 TIOBE Programming Community Index

TIOBE Programming Community Index 是一個用來評量程式語言受歡迎程度的指標,其排名方式,在頁面上說明如下:

The index is updated once a month. The ratings are based on the number of skilled engineers world-wide, courses and third party vendors. The popular search engines Google, MSN, Yahoo!, Wikipedia and YouTube are used to calculate the ratings.

我大概每個月初都會略微的瞄一下,了解一下目前各主要程式語言的排名狀況。昨天在檢視的時候,發現了 Sun 推了一陣子的 JavaFX 語言,並沒有在排名裡面。可能原因是 JavaFX 並沒有在百大排名裡面,或者是 TIOBE 並沒注意到這個程式語言。而我發現網頁上有這一行字:

If you have the impression there is a programming language lacking, please notify us at tpci@tiobe.com.

也就是說,如果你發現 TIOBE 可能漏掉某個程式語言的排名,可以寫信告訴他們。因此,我想先確定一下,JavaFX 目前是否有資格擠進百大。我用了一個「比較法」來確認,也就是,如果某語言在 Top 100 或 Top 50,而 JavaFX 的熱門程式與該語言相當,那麼 JavaFX 也該在 Top 100 或 Top 50 左右。

這個用來做比較的語言,最好不要有一字多義的傾向。例如,Ruby、Python、Groovy、Alice 等就都不適合,因為他們不是一字多義,就是菜市場名字。後來我決定以 Erlang 來做比較,比較結果如下:

就查詢量來看,javafx 有超越 erlang 的趨勢。有了這個發現,我便動筆寫了信給 TIOBE 的成員,而沒想到昨天才寄的信,今早就收到回信。而 TIOBE 也把 JavaFX 納入排名,目前排名第 72。另一個值得小小高興一下的是,TIOBE 的回信者 Paul 在信中說道,為了感謝我的建議,他會在最近一期的排名中公提到我的名字。看看 6 月份的排名,哇,真的有提到我耶 ^_^!

2009/05/05

有趣的 Ruby 字串操作

irb(main):025:0> a= "abc def"
=> "abc def"
irb(main):026:0> a["def"] = "abc"
=> "abc"
irb(main):027:0> a
=> "abc abc"
irb(main):028:0> a["abc"] = "xyz"
=> "xyz"
irb(main):029:0> a
=> "xyz abc"
irb(main):030:0> a["abc"]
=> "abc"
irb(main):031:0> a["def"]
=> nil
irb(main):032:0>

date = "2009/12/12"
month, day, year = date.split('/')

Technorati : ,