本文討論將正則表達式與 Java ResourceBundle 相結合的一種數據驗證技術 。Java 語言對正則表達式的支持可以大大簡化數據驗證。您可以將數據與正則 表達式進行比較,如果它們匹配,則知道數據是有效的。另一方面,Java ResourceBundle 包含翻譯好的字符串,用於匹配用戶機器上的當前語言和國家 設置。ResourceBundle 中的字符串通常是出現在應用程序中的文本,但是也可 以是特定於某個地區的任何東西。
您將實踐一個示例應用程序,該應用程序從 ResourceBundles 獲得正則表達 式,並將它們用於數據驗證(請參見 下載 小節)。通過這種方法,就可以用一 塊代碼來驗證很多不同類型的數據。更妙的是,隨著更多 ResourceBundle 的添 加,還可以驗證更多類型的數據,並且不用更改這段代碼中的任何一行。
本文的示例應用程序是在 Eclipse 中用 Visual Editor 構建的。Visual Editor 是一種用於構建圖形化界面的開放源碼工具。為了構建自己的應用程序 ,您需要在計算機上安裝 Eclipse 和 Visual Editor 包(請參閱 參考資料) 。這個示例應用程序只是舉例說明了驗證數據的一種技巧,所以這種方法可用於 任何 Java 應用程序。
示例應用程序
我不想花太多的時間討論這個示例應用程序的所有細節,我只關注其中的數 據驗證方面的技巧。這個應用程序驗證輸入到輸入域中的郵政編碼。您可能知道 ,在世界的不同地方,郵政編碼千差萬別。有的是數字,有的則包含字母。即使 同是由數字組成的郵政編碼,在不同地方其長度也不盡相同。有的國家以特定的 模式排列字母和數字,而另外一些國家則采用更自由的格式。所有這些格式都可 以用正則表達式來描述。例如,在美國郵政編碼是一個五位數,後面還可能跟有 一個破折號加一個四位數。清單 1 展示了描述這種格式的正則表達式:
清單 1. 用於美國郵政編碼的正則表達式
[0-9]{5}(-[0-9]{4})?
除了格式不同外,郵政編碼並不總是被稱為郵政編碼。例如,美國將郵政編 碼稱為 ZIP Code。ResourceBundle 的一種常見用法就是處理這種類型的與地區 有關的差異。用於美國的 ResourceBundle 可能包含短語 "Enter your ZIP Code",而在用於加拿大的 ResourceBundle 中,相應的短語可能是 "Enter your postal code"。我在本文中演示的技巧也是從 ResourceBundle 獲得用於有效郵政編碼的正則表達式。
為了使這個示例簡單化,您將創建一個只有一個輸入域和一個 Validate 按 鈕的 Swing 應用程序。用戶在輸入域中輸入文本,然後單擊該按鈕。如果數據 與當前的正則表達式匹配,則應用程序顯示一條消息,表明郵政編碼有效。因為 應用程序使用不同的 ResourceBundle,所以正則表達式隨著有效數據的規則的 變化而變化。由於正則表達式是從文本文件中裝載的一個字符串,所以當添加對 新類型的郵政編碼的支持時,不需要更改代碼。
您將在 Eclipse 中使用 Eclipse Visual Editor 和 Eclipse Java Development Tool 的一些特性來構建這個應用程序。您可以在幾乎所有開發環 境中使用這種技巧。這裡的代碼應該可以在任何基於 Eclipse 的產品中運行, 例如 Rational Application Developer(請參閱 參考資料)。
圖 1 展示了該應用程序在 Eclipse Visual Editor 中的樣子:
圖 1. Eclipse Visual Editor 中的示例應用程序
Visual Editor 提供了四種查看應用程序的方式。在屏幕的頂端是應用程序 的可視化圖像,源代碼在底端。Eclipse 還提供了兩個視圖 —— Properties 視圖和 Java Beans 視圖 —— 可以通過這兩個視圖來 處理應用程序。所有這些查看應用程序的方式都是由 Eclipse Modeling Framework (EMF) 控制的。由於已經有一些關於 EMF 的完整書籍,所以我不會 再談更多的細節。從程序員的角度來看,重要的一點是,任何視圖中的變化都會 自動發送到其他視圖。例如,如果您使用 Properties 視圖將一個對象的背景顏 色設為綠色,那麼可視化圖像和源代碼也會自動更新。
運行初始的示例應用程序
首先來看一個已經創建好的應用程序(參見 下載 小節)。圖 2 展示了這個 應用程序的運行界面:
圖 2. 輸入有效數據時的示例應用程序
在圖 2 中,用戶輸入了有效的數據,並單擊了 Validate 按鈕。如果數據無 效,那麼將出現圖 3 所示的界面:
圖 3. 輸入無效數據時的示例應用程序
清單 2 展示了如何使用 清單 1 中的正則表達式來驗證數據:
清單 2. 使用正則表達式
Pattern pc = Pattern.compile("[0-9]{5}(-[0-9]{4})? ");
Matcher m = pc.matcher(postalCode.getText());
if (m.matches())
{
validLabel.setText("Your postal code is valid.");
validLabel.setForeground(Color.BLUE);
}
else
{
validLabel.setText("Your postal code is not valid.");
validLabel.setForeground(Color.RED);
}
清單 2 中的兩條反饋消息通常會被翻譯成其他語言。您還將通過使用這裡展 示的技巧來 “翻譯” 正則表達式。與一般的翻譯不同,將正則表達 式轉換成國際化版本是數據格式專家的工作,而不是語言專家的工作。
具體化字符串
Eclipse 為代碼的國際化提供了一個方便的特性。首先單擊 Source > Externalize Strings...,如圖 4 所示:
圖 4. Externalize Strings... 主菜單
Eclipse 查看 Java 代碼,以發現應該放入到 ResourceBundle 中的字符串 。您將看到類似圖 5 所示的對話框:
圖 5. Externalize Strings 對話框
在圖 5 中列出的所有字符串中,對話框頂部的空白字符串不需要翻譯。(反 饋消息的初始值是一個空白字符串。)取消對第一個字符串的選擇,然後單擊 Next 和 Finish。Eclipse 創建一個新的名為 com.ibm.developerworks.Messages 的類,這個類從 messages.properties 文 件獲取字符串。
處理國際化代碼
具體化代碼之後,Eclipse 修改初始的類,將字符串移入 messages.properties 文件,並創建一個名為 Messages 的新類。Messages 類 有一個名為 getString() 的靜態方法,應用程序將使用該方法來獲得字符串的 值。
Messages 類在內部使用 ResourceBundle。清單 3 展示了生成的用於創建 ResourceBundle 的代碼:
清單 3. 創建 ResourceBundle
public class Messages {
private static final String BUNDLE_NAME =
"com.ibm.developerworks.messages"; //$NON-NLS-1$
private static final ResourceBundle RESOURCE_BUNDLE =
ResourceBundle.getBundle(BUNDLE_NAME);
稍後我將更詳細地談到如何創建 ResourceBundle。
所有字符串的值都在 messages.properties 文件中,如清單 4 所示:
清單 4. messages.properties 文件
LocalizedValidator.1=News Gothic
LocalizedValidator.2=Validate
LocalizedValidator.3=[0-9]{5}(-[0-9]{4})?
LocalizedValidator.4=Your postal code is valid.
LocalizedValidator.5=Your postal code is not valid.
LocalizedValidator.6=Exit
LocalizedValidator.7=Localized Data Validator
LocalizedValidator.8=Enter your postal code, then click Validate:
從技術上說,該文件是 com/ibm/developerworks/messages.properties,但 是您不必關心這個細節。生成的代碼可以正確無誤地找到該文件。
使用 ResourceBundle 來驗證數據
當使用 Eclipse Externalize Strings 功能創建 .properties 文件時,它 修改了應用程序,以便同時獲取正則表達式和程序中所有其他可翻譯的文本,如 清單 5 所示:
清單 5. 通過 ResourceBundle 使用正則表達式
Pattern pc = Pattern.compile(Messages.getString ("LocalizedValidator.3"));
//$NON-NLS-1$
Matcher m = pc.matcher(postalCode.getText());
if (m.matches())
{
validLabel.setText(Messages.getString ("LocalizedValidator.4"));
//$NON-NLS-1$
validLabel.setForeground(Color.BLUE);
}
else
{
validLabel.setText(Messages.getString ("LocalizedValidator.5"));
//$NON-NLS-1$
validLabel.setForeground(Color.RED);
}
注意,Pattern.compile() 方法使用 Messages.getString() 方法來獲得正 則表達式的值。當需要驗證數據時,代碼首先獲得字符串 LocalizedValidator.3,然後使用它來驗證郵政編碼。反饋消息也是從 properties 文件獲得的。
如何裝載 .properties 文件
至此,主應用程序已經可以使用 “翻譯” 好的正則表達式了。 所有字符串的值都來自 messages.properties 文件,那麼,如何裝載這些字符 串的不同版本呢?答案取決於 ResourceBundle 是如何創建的。
無論何時運行一個 Java 程序,它都有一個特定的地區。地區由兩個字母的 語言代碼和兩個字母的國家代碼來指定,這些代碼是由 ISO 標准定義的。地區 代碼還有一個不常用的變種部分,用於更精確地指定特定的地區。下面是一些例 子:
en_US 是 U.S. English 地區。
en_CA 是 Canadian English 地區。
fr_CA 是 French Canadian 地區。
en 是 English 地區。
en_US_UNIX 是 U.S. English 地區的 UNIX 變種。至於該變種的意義及其用 法,是由應用程序的編寫者定義的。
當創建一個新的 ResourceBundle 時,Java 運行時根據當前的地區查找文件 。例如,如果當前地區是 en_US,那麼 Java 運行時依次查找以下文件:
messages_en_US.properties
messages_en.properties
messages.properties
當 ResourceBundle 收集翻譯好的字符串時,在 messages_en_US.properties 中發現的任何字符串都具有比 messages_en.properties 和 messages.properties 中具有相同名稱的字符串更 高的優先級。如果運行時沒有發現任何特定於地區的文件,那麼它將使用 messages.properties 中的字符串。
記住,創建 ResourceBundle 的代碼指定了文件名 messages.properties。 該文件名不會隨著地區的改變而改變,這意味著您的代碼也不需要做出更改。您 只需指定這個文件名,Java 運行時可以自動得出應該裝載哪個特定於地區的文 件。
特定於地區的 .properties 文件
一個特定於地區的 .properties 文件只包含不同於更通用的 .properties 文件的字符串。例如,如果 messages_en.properties 文件包含 LocalizedValidator.9=What is your favorite color? 這一行,那麼 messages_en_GB.properties 文件可能包含 LocalizedValidator.9=What is your favourite colour?。如果只有這個英國化的字符串是 en_GB 地區所特有 的,那麼 messages_en_GB.properties 文件只需包含這個字符串。當代碼向 ResourceBundle 請求任何其他字符串時,如果 messages_en.properties 中有 這樣的字符串,就使用其中的字符串。如果 messages_en.properties 文件中沒 有那樣的字符串,則使用 messages.properties 中的版本。如果這一系列的 .properties 文件中都沒有被請求的字符串,就會拋出 java.util.MissingResourceException 異常。
清單 6 展示了美國地區的 .properties 文件所特有的一些行:
清單 6. en_US(美國)地區特有的值
LocalizedValidator.4=Your ZIP Code is valid.
LocalizedValidator.5=Your ZIP Code is not valid.
LocalizedValidator.8=Enter your ZIP Code, then click Validate:
這裡惟一的變化是使用 "ZIP Code" 代替 "postal code"。可用默認的正則表達式驗證該數據。
英國的郵政編碼有六種不同的格式,還有一個特殊的值 GIR 0AA,如清單 7 所示。(為了便於閱讀,清單 7 中的正則表達式被分成兩行,實際上只有一行 。)
清單 7. en_GB(英國)地區特有的值
LocalizedValidator.3=
[A-Z]([0-9]|[0-9]{2}|[A-Z][0-9]|[A-Z][0-9]{2}|[0-9][A-Z]
|[A-Z][0-9][A-Z]) [0-9][A-Z]{2}|GIR 0AA
LocalizedValidator.8=Enter your postcode, then click Validate:
用於澳大利亞的正則表達式包括州或地區的簡稱,需要的空格(一個或兩個 ),以及一個四位數,如清單 8 所示:
清單 8. en_AU(澳大利亞)地區特有的值
LocalizedValidator.3=(ACT|NSW|NT|QLD|SA|TAS|VIC|WA)( | )[0- 9]{4}
LocalizedValidator.8=Enter your Australian postal code, then click Validate:
加拿大的郵政編碼格式是字母、數字、字母、一個空格、數字、字母、數字 ,如清單 9 所示:
清單 9. en_CA(加拿大)地區特有的值
LocalizedValidator.3=[A-Z][0-9][A-Z] [0-9][A-Z][0-9]
LocalizedValidator.8=Enter your Canadian postal code, then click Validate:
清單 10 展示了德國地區特有的值。德國的郵政編碼是一個五位數:
清單 10. de(德國)地區特有的值
LocalizedValidator.2=Validieren
LocalizedValidator.3=[0-9]{5}
LocalizedValidator.4=Ihre Postleitzahl ist gÜltig
LocalizedValidator.5=Ihre Postleitzahl ist ungÜltig!
LocalizedValidator.6=Beenden
LocalizedValidator.7=NLS Datenvalidatung
LocalizedValidator.8=Geben Sie Ihre Postleitzahl ein
在運行時設置地區
現在您已經定義了 .properties 文件,接下來應該用兩種方法中的一種來測 試這些文件。第一種方法是在運行應用程序的時候設置 user.language 和 user.country 這兩個系統屬性。在 Eclipse 環境中,可以右鍵單擊一個類名, 然後選擇 Run... 菜單,如圖 6 所示:
圖 6. Run... 菜單
在 Run 對話框中,可以設置 Java VM 選項,以改變默認的語言和地區,如 圖 7 所示:
圖 7. 在 Run 對話框中設置 Java VM 參數
-D 選項用於定義系統屬性。您可以在命令行中使用相同的語法,例如:
java -Duser.language=en -Duser.country=AU com.ibm.developerworks.LocalizedValidator
第二種方法是在應用程序中設置地區。通過 Locale.setDefault() 方法可以 在代碼中設置默認的地區。清單 11 展示了如何改變 LocalizedValidator 類的 main() 方法:
清單 11. 在應用程序中設置默認的地區
public static void main(String[] args) {
String language = "";
String country = "";
if (args.length > 0)
language = args[0];
if (args.length > 1)
country = args[1];
Locale.setDefault(new Locale(language, country));
LocalizedValidator nv = new LocalizedValidator();
nv.show();
}
如果在命令行沒有指定參數,則使用用戶計算機的默認地區。如果沒有命令 行參數,則代碼 new Locale("", "") 只是創建默認的地 區。
也可以在 Run 對話框中設置命令行參數,如圖 8 所示:
圖 8. 在 Run 對話框中設置命令行參數
圖 9 展示了指定了參數 en AU 的情況下應用程序的界面:
圖 9. 用於澳大利亞地區(en_AU)的示例應用程序
用參數 de 運行示例應用程序時,將得到如圖 10 所示的界面:
圖 10. 用於德國地區(de)的示例應用程序
結束語
本文展示了如何將正則表達式與 Java 語言的國際化支持相結合來驗證不同 類型的本地化數據。通過這種技巧,您可以支持新的數據類型,而不用更改任何 代碼。例如示例應用程序,如果您想添加對波蘭的郵政編碼的支持,那麼只需創 建一個 messages_pl.properties 文件。這樣就在沒有更改任何代碼的情況下添 加了對新數據類型的支持。(如果您想知道的話,那麼告訴您,用於波蘭的郵政 編碼的正則表達式是 [0-9]{2}-?[0-9]{3}。)
示例應用程序原封不動地使用 Eclipse 生成的 Messages 類。這個類能滿足 這個例子的要求,但是,應用程序啟動時會裝載 ResourceBundle,並且直到下 次運行應用程序時才能重新裝載 ResourceBundle。如果您想更改代碼,以便動 態地改變 ResourceBundle,那麼需要修改 Messages 類,使它的字段和方法不 是靜態的。這做起來不難,但是您還需要修改和維護 Messages.java 文件。就 把這個任務作為練習吧。
還應該認識到,Swing 提供了 javax.swing.JFormattedTextField 類。利用 這個類可以為文本域定義一個掩碼。例如,您可以使用掩碼 (###) ###-####, 使用戶只能在文本域中輸入有效的美國的電話號碼。您可以使用與這裡相同的技 巧來從一個本地化的 ResourceBundle 中獲得掩碼字符串。
JFormattedTextField 類有明顯的優勢,因為它可以在用戶輸入時驗證數據 ,為用戶提供直接的反饋。但是掩碼字符串不如正則表達式那麼靈活。例如,您 可以為美國的 ZIP Code 編寫掩碼 ##### 或 #####-####,但是不能同時使用這 兩個掩碼。如果一個掩碼字符串足以處理一組本地化數據類型,那麼從 ResourceBundle 獲得掩碼字符串就是本技巧的一個很好的用途。(請參閱 參考 資料,以找到關於擴展 JFormattedTextField 類的行為的文章。)
本文配套源碼