1. 程式人生 > 實用技巧 >異常與日誌

異常與日誌

捕獲異常

捕獲異常

如果某個異常發生的時候沒有在任何地方進行捕獲,那程式就會終止執行,並在控制檯 上打印出異常資訊, 其中包括異常的型別和堆疊的內容。

要想捕獲一個異常, 必須設定 try/catch語句塊。最簡單的 try語句塊如下所示:

try{
    code
    more code
    more code
} catch (ExceptionType e) { 
    handlerfor this type 
} 

如果在 try語句塊中的任何程式碼丟擲了一個在 catch子句中說明的異常類,那麼

1 ) 程式將跳過 try語句塊的其餘程式碼。

2 ) 程式將執行 catch 子句中的處理器程式碼。

如果在 try 語句塊中的程式碼沒有拋出任何異常,那麼程式將跳過 catch 子句。

如果方法中的任何程式碼拋出了一個在 catch 子句中沒有宣告的異常型別,那麼這個方法 就會立刻退出(希望呼叫者為這種型別的異常設tKT catch 子句)。

如果想傳遞一個異常, 就必須在方法的首部新增一個 throws說明符, 以便告知呼叫者這 個方法可能會丟擲異常。

仔細閱讀一下 Java API 文件, 以便知道每個方法可能會丟擲哪種異常, 然後再決定是自己處理,還是新增到 throws 列表中。對於後一種情況,也不必猶豫。將異常直接交給能夠勝 任的處理器進行處理要比壓制對它的處理更好。

如果編寫一個覆蓋超類的方法, 而這個方法又沒有丟擲異常(如 JComponent 中的 paintComponent), 那麼這個方法就必須捕 獲方法程式碼中出現的每一個受查異常。不允許在子類的 throws說明符中出現超過超類方法所 列出的異常類範圍。

捕獲多個異常

在一個 try語句塊中可以捕獲多個異常型別,並對不同型別的異常做出不同的處理。可 以按照下列方式為每個異常型別使用一個單獨的 catch 子句:

try { 
​
code that might throwexceptions 
​
} catch (FileNotFoundException e) {
​
 emergencyactionfor missingfiles
​
 } catch (UnknownHostException e) {
​
 emergencyactionfor unknown hosts 
​
} catch (IOException e) {
​
 emergencyactionforallother I
/Oproblems ​ }

異常物件可能包含與異常本身有關的資訊。要想獲得物件的更多資訊, 可以試著使用

e.getHessage()

得到詳細的錯誤資訊(如果有的話),或者使用

e.getClass().getName()

得到異常物件的實際型別。

在 Java SE 7中,同一個 catch 子句中可以捕獲多個異常型別。例如,假設對應缺少檔案 和未知主機異常的動作是一樣的,就可以合併 catch 子句:

try {
codethat might throwexceptions
} catch (FileNotFoundException | UnknownHostException e) {
    emergencyactionfor missing filesand unknown hosts 
} catch (IOException e) {
    emergencyactionforall other I/O problems 
} 

只有當捕獲的異常型別彼此之間不存在子類關係時才需要這個特性。

[注] 捕獲多個異常時, 異常變數隱含為 final 變數。例如,不能在以下子句體中為 e 賦 不同的值: catch (FileNotFoundException | UnknownHostException e) { . . . } [注] 捕獲多個異常不僅會讓你的程式碼看起來更簡單, 還會更高效。 生成的位元組碼只包含一個對應公共 catch 子句的程式碼塊。

再次丟擲異常與異常鏈

在 catch 子句中可以丟擲一個異常,這樣做的目的是改變異常的型別。 如果開發了一個 供其他程式設計師使用的子系統, 那麼,用於表示子系統故障的異常型別可能會產生多種解釋。 ServletException 就是這樣一個異常型別的例子。

try { 
    access thedatabase 
} catch (SQLException e) { 
    throw new ServletException("database error: " + e.getMessageO); 
} 

這裡,ServleException 用帶有異常資訊文字的構造器來構造。

不過,可以有一種更好的處理方法,並且將原始異常設定為新異常的“ 原因”:

try {
accessthedatabase
}catch (SQLException e) {
    Throwable se = new ServletException("database error"); 
    se.initCause(e); 
    throw se;
}

當捕獲到異常時, 就可以使用下面這條語句重新得到原始異常:

Throwable e = se.getCause();

強烈建議使用這種包裝技術。這樣可以讓使用者丟擲子系統中的高階異常,而不會丟失原始異 常的細節。

[注] 如果在一個方法中發生了一個受查異常, 而不允許丟擲它, 那麼包裝技術就十分 有用。我們可以捕獲這個受查異常,並將它包裝成一個執行時異常。

finally 子句

不管是否有異常被捕獲,finally 子句中的程式碼都被執行。在下面的示例中, 程式將在所 有情況下關閉檔案。

InputStream in = new FileInputStream(. ..); 
​
try{
​
    //1 
    code that might throwexceptions 
    //2
} catch (IOException e) { 
    // 3 
    showerror message 
    // 4 
} finally {
    // 5 
    in.close();
}
//6

在上面這段程式碼中,有下列 3 種情況會執行 finally 子句:

1 ) 程式碼沒有丟擲異常。這種情況下只執行標註的1、2、5、6。

2 ) 丟擲一個在 catch 子句中捕獲的異常。在上面的示例中就是 IOException 異常。這種情況下只執行標註的1、3、4、5、6。

3 ) 程式碼丟擲了一個異常, 但這個異常不是由 catch 子句捕獲的。在這種情況下只執行標註的1、5。

這裡, 強烈建議解搞合 try/catch 和 try/finally 語句塊。這樣可以提高程式碼的清晰 度。例如:

InputStrean in = . . .; 
​
try { 
​
    try { 
​
        code that might throwexceptions 
    } finally { 
        in.close(); 
    } 
} catch (IOException e) {
    showerrormessage 
}

內層的 try語句塊只有一個職責, 就是確保關閉輸入流。外層的 try語句塊也只有一個職 責, 就是確保報告出現的錯誤。這種設計方式不僅清楚, 而且還具有一個功能,就是將 會報告 finally 子句中出現的錯誤。

帶資源的 try語句

帶資源的 try語句(try-with-resources) 的最簡形式為:

 try (Resource res = . . .) {
​
    work with res
​
 } 

try塊退出時,會自動呼叫 res.dose()。下面給出一個典型的例子, 這裡要讀取一個檔案 中的所有單詞:

try (Scanner in = new Scanner(new FileInputStream("7usr/share/dict/words")), "UTF-8") { 
​
    while (in.hasNext()) 
​
        System.out.println(in.next()); 
​
}

這個塊正常退出時, 或者存在一個異常時, 都會呼叫 in.close()方法, 就好像使用了 finally塊一樣。

還可以指定多個資源: 例如:

 try (Scanner in = new Scanner(new FileInputStream("7usr/share/dict/words")."UTF-8"): PrintWriter out = new PrintWriter("out.txt")) 
{ 
    while (in.hasNextO)
        out.println(in.next().toUpperCaseO); 
} 

不論這個塊如何退出, in 和 out 都會關閉。如果你用常規方式手動程式設計,就需要兩個嵌 套的 try/finally語句。

[注] 帶資源的 try 語句自身也可以有 catch 子句和一個 finally 子句。 這些子句會在 關閉資源之後執行。 不過在實際中, 一個 try 語句中加入這麼多內容可能不是一個好主意。

分析堆疊軌跡元素

堆疊軌跡(stack trace) 是一個方法呼叫過程的列表, 它包含了程式執行過程中方法呼叫 的特定位置。

可以呼叫 Throwable 類的 printStackTrace 方法訪問堆疊軌跡的文字描述資訊

Throwable t = new Throwable(); 
StringWriter out = new StringWriter(); 
t.printStackTrace(new PrintWriter(out)); 
String description = out.toString();

一種更靈活的方法是使用 getStackTrace 方法, 它會得到 StackTraceElement 物件的一個 陣列, 可以在你的程式中分析這個物件陣列。例如:

Throwable t = new ThrowableO; 
StackTraceElement[] frames = t.getStackTrace(); 
for (StackTraceElement frame : frames) 
    analyzeframe

StackTraceElement 類含有能夠獲得檔名和當前執行的程式碼行號的方法, N時, 還含有 能夠獲得類名和方法名的方法。toString 方法將產生一個格式化的字串, 其屮包含所獲得 的資訊。

靜態的 Thread.getAllStackTrace 方法, 它可以產生所有執行緒的堆疊軌跡 . 下面給出使用 這個方法的具體方式:

Map<Thread, StackTraceElement[]> map = Thread.getAl1StackTraces(); 
for (Thread t : map.keySet()) { 
    StackTraceElement[] frames = map.get(t); 
    analyze frames 
}

程式清單 7-1 列印了遞迴階乘函式的堆疊情況例如, 如果計算 factorials), 將會列印 下列內容:

factorial(3): 
StackTraceTest.factorial(StackTraceTest.java:18) StackTraceTest.main(StackTraceTest.java:34) 
factorial(2): 
StackTraceTest.factorial(StackTraceTest.java:18) StackTraceTest.factorial(StackTraceTest.java:24) StackTraceTest.main(StackTraceTest.java:34) 
factorial(1): 
StackTraceTest.factorial(StackTraceTest.java:18) StackTraceTest.factorial(StackTraceTest.java:24) StackTraceTest.factorial(StackTraceTest.java:24) 
StackTraceTest.main(StackTraceTest ava:34) 
return 1 
return 2 
return 6

//程式清單 7-1 stackTrace/StackTraceTest.java
package stackTrace;
​
import java.util.*;
​
/**
 * A program that displays a trace feature of a recursive method call.
 * @version 1.01 2004-05-10
 * @author Cay Horstmann
 */
public class StackTraceTest
{
   /**
    * Computes the factorial of a number
    * @param n a non-negative integer
    * @return n! = 1 * 2 * . . . * n
    */
   public static int factorial(int n)
   {
      System.out.println("factorial(" + n + "):");
      Throwable t = new Throwable();
      StackTraceElement[] frames = t.getStackTrace();
      for (StackTraceElement f : frames)
         System.out.println(f);
      int r;
      if (n <= 1) r = 1;
      else r = n * factorial(n - 1);
      System.out.println("return " + r);
      return r;
   }
​
   public static void main(String[] args)
   {
      Scanner in = new Scanner(System.in);
      System.out.print("Enter n: ");
      int n = in.nextInt();
      factorial(n);
   }
}


使用異常機制的技巧

下面給出使用異常機制的幾個技巧。

  1. 異常處理不能代替簡單的測試。

  1. 不要過分地細化異常。

  2. 利用異常層次結構。

  3. 不要壓制異常 。

  4. 在檢測錯誤時, “ 苛刻” 要比放任更好 。

  5. 不要羞於傳遞異常。

使用斷言

斷言的概念

假設確信某個屬性符合要求,並且程式碼的執行依賴於這個屬性。例如, 需要計算

double y = Math.sqrt(x);

我們確信,這裡的 X 是一個非負數值。原因是:X 是另外一個計算的結果,而這個結果 不可能是負值;或者 X 是一個方法的引數,而這個方法要求它的呼叫者只能提供一個正整數。 然而,還是希望進行檢查, 以避免讓“ 不是一個數” 的數值參與計算操作。當然,也可以拋 出一個異常:

if (x < 0) throw new IllegalArgumentException("x < 0");

但是這段程式碼會一直保留在程式中, 即使測試完畢也不會自動地刪除。如果在程式中含 有大量的這種檢查,程式執行起來會相當慢。

斷言機制允許在測試期間向程式碼中插入一些檢査語句。當代碼釋出時,這些插人的檢測語句將會被自動地移走。

Java語言引人了關鍵字 assert。這個關鍵字有兩種形式:

assert 條件;

assert 條件:表示式;

這兩種形式都會對條件進行檢測, 如果結果為 false, 則丟擲一個 AssertionError 異常。 在第二種形式中,表示式將被傳人 AssertionError 的構造器,並轉換成一個訊息字串。

[注] “ 表示式” 部分的唯一目的是產生一個訊息字串。AssertionError 物件並不儲存 表示式的值, 因此, 不可能在以後得到它。正如 JDK 文件所描述的那樣: 如果使用表達 式的值, 就會鼓勵程式設計師試圖從斷言中恢復程式的執行, 這不符合斷言機制的初衷。

要想斷言?c 是一個非負數值, 只需要簡單地使用下面這條語句

assert x >= 0;

或者將 x 的實際值傳遞給 AssertionError 物件, 從而可以在後面顯示出來。

assert x >= 0 : x;

啟用和禁用斷言

在預設情況下,斷言被禁用。可以在執行程式時用 -enableassertions 或 -ea選項啟用:

java -enableassertions MyApp

需要注意的是, 在啟用或禁用斷言時不必重新編譯程式。啟用或禁用斷言是類載入器 ( class loader) 的功能。當斷言被禁用時, 類載入器將跳過斷言程式碼, 因此,不會降低程式運 行的速度。

也可以在某個類或整個包中使用斷言, 例如:

java -ea:MyClass -eaiconi.inycompany.inylib.., MyApp

這條命令將開啟 MyClass類以及在 com.mycompany.mylib 包和它的子包中的所有類的斷 言。選項 -ea 將開啟預設包中的所有類的斷言。

也可以用選項-disableassertions 或 -da 禁用某個特定類和包的斷言:

java -ea:... -da:MyClass MyApp

有些類不是由類載入器載入, 而是直接由虛擬機器載入。可以使用這些開關有選擇地啟用 或禁用那些類中的斷言。

然而, 啟用和禁用所有斷言的 -ea 和 -da 開關不能應用到那些沒有類載入器的“ 系統類” 上。對於這些系統類來說, 需要使用-enablesystemassertions/-esa 開關啟用斷言。

使用斷言完成引數檢查

在 Java 語言中, 給出了 3 種處理系統錯誤的機制:

•丟擲一個異常

•日誌

•使用斷言

什麼時候應該選擇使用斷言呢? 請記住下面幾點:

•斷言失敗是致命的、 不可恢復的錯誤。

•斷言檢查只用於開發和測階段

記錄曰志

基本曰志

要生成簡單的日誌記錄,可以使用全域性日誌記錄器(global logger) 並呼叫其 info方法:

Logger.getClobal0,info("File->Open menu item selected");

在預設情況下,這條記錄將會顯示以下內容:

May 10, 2013 10:12:15 PM LogginglmageViewer fileOpen

INFO: File->0pen menu item selected

但是, 如果在適當的地方(如 main 開始)呼叫

Logger.getClobal().setLevel(Level.OFF);

將會取消所有的日誌。

高階曰志

在一 個專業的應用程式中,不要將所有的日誌都記錄到一個全域性日誌記錄器中,而是可以自定義 日誌記錄器。

可以呼叫 getLogger方法建立或獲取記錄器:

private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");

[注] 未被任何變數引用的日誌記錄器可能會被垃圾回收。為了防止這種情況發生,要 像上面的例子中一樣, 用一個靜態變數儲存日誌記錄器的一個引用。

通常, 有以下 7 個日誌記錄器級別:

• SEVERE

• WARNING

• INFO

• CONFIG

• FINE

• FINER

• FINEST

在預設情況下,只記錄前三個級別。 也可以設定其他的級別。例如,

logger,setLevel(Level.FINE);

現在, FINE 和更高級別的記錄都可以記錄下來。 另外, 還可以使用 Level.ALL 開啟所有級別的記錄, 或者使用 Level.OFF 關閉所有級別 的記錄。 對於所有的級別有下面幾種記錄方法:

logger.warning(message): logger,fine(message);

同時,還可以使用 log方法指定級別, 例如:

logger.log(Level.FINE, message);

[提示] 預設的日誌配置記錄了 INFO 或更高級別的所有記錄, 因此, 應該使用 CONFIG、 FINE, FINER 和 FINEST 級別來記錄那些有助於診斷,但對於程式設計師又沒有太大意義的 除錯資訊。

修改日誌管理器配置

可以通過編輯配置檔案來修改日誌系統的各種屬性。在預設情況下,配置檔案存在於:

jre/lib/1ogging.properties

要想使用另一個配置檔案, 就要將 java.utiUogging.config.file 特性設定為配置檔案的存 儲位置, 並用下列命令啟動應用程式:

java -Djava.util.logging.config.file-configFileMainClass

[警告] 日誌管理器在 VM 啟動過程中初始化, 這在 main 執行之前完成。 如果在 main 中呼叫 System.setProperty("java.util_logging.config_file",file), 也 會 調 用 LogManager. readConfiguration() 來重新初始化曰志管理器。

要想修改預設的日誌記錄級別, 就需要編輯配置檔案,並修改以下命令列

.level=INFO

可以通過新增以下內容來指定自己的日誌記錄級別

com.mycompany_myapp.level=FINE

也就是說,在日誌記錄器名後面新增字尾 .level。

另外,處 理器也有級別。要想在控制檯上看到 FINE級別的訊息, 就需要進行下列設定

java.util.logging.ConsoleHandler.level=FINE

本地化

本地化的應用程式包含資源包(resource bundle) 中的本地特定資訊。資源包由各個地區 (如美國或德國)的對映集合組成。例如, 某個資源包可能將字串“ readingFile” 對映成英 文的 “ Reading file” 或者德文的“ Achtung! Datei wird eingelesen”。

一個程式可以包含多個資源包, 一個用於選單;其他用於日誌訊息。每個資源包都有一個名字(如 com.mycompany.logmessages)。要想將對映新增到一個資源包中,需要為每個地 區建立一個檔案。英文訊息對映位於 com/mycompany/logmessages_en.properties 檔案中; 德 文訊息對映位於 com/mycompany/logmessages_de.properties 檔案中。(en 和 de 是語言編碼)。

處理器

在預設情況下t 日誌記錄器將記錄傳送到 ConsoleHandler 中, 並由它輸出到 System.err 流中。特別是,日誌記錄器還會將記錄傳送到父處理器中,而最終的處理器(命名為“ ”)有 一個 ConsoleHandler。

與日誌記錄器一樣,處理器也有日誌記錄級別。對於一個要被記錄的日誌記錄,它的日 志記錄級別必須高於日誌記錄器和處理器的閾值。日誌管理器配置檔案設定的預設控制檯處 理器的日誌記錄級別為

java.uti1.1ogging.ConsoleHandler.level=INF0

要想記錄 FINE 級別的日誌,就必須修改配置檔案中的預設日誌記錄級別和處理器級別。 另外,還可以繞過配置檔案,安裝自己的處理器。

Logger logger = Logger.getLogger("com.mycompany.myapp");

logger.setLevel(Level.FINE);

logger .setUseParentHandlers(false);

Handler handler = new ConsoleHandler();

handler,setLevel(Level.FINE);

logger.addHandler(hand er):

在預設情況下, 日誌記錄器將記錄傳送到自己的處理器和父處理器。我們的日誌記錄 器是原始日誌記錄器(命名為“ ”)的子類, 而原始日誌記錄器將會把所有等於或高於 INFO級別的記錄傳送到控制檯。然而, 我們並不想兩次看到這些記錄。鑑於這個原因,應該將 useParentHandlers M性設定為 false。

可以通過設定 H 志管理器配置檔案中的不同引數(請參看表 7-1 ), 或者利用其他的構造 器(請參看本節後面給出的 APf 註釋)來修改檔案處理器的預設行為。

也冇可能不想使用預設的日誌記錄檔名, 因此, 應該使用另一種模式, 例如, %h/ myapp.log ( 有關模式變數的解釋請參看表 7-2 )。

如果多個應用程式(或者同一個應用程式的多個副本)使用同一個口志文件, 就應該開 啟 append 標誌。另外, 應該在檔名模式中使用 %u, 以便每個應用程式建立日誌的唯一 副本。

過濾器

在預設情況下, 過濾器根據日誌記錄的級別進行過濾。每個日誌記錄器和處理器都可以 有一個可選的過濾器來完成附加的過濾。另外,可以通過實現 niter 介面並定義下列方法來 自定義過濾器。

boolean isLoggab1e(LogRecord record)

在這個方法中,可以利用自己喜歡的標準,對日誌記錄進行分析,返回 true 表示這些記 錄應該包含在日誌中。

要想將一個過濾器安裝到一個日誌記錄器或處理器中,只需要呼叫 setFilter方法就可以 了。注意,同一時刻最多隻能有一個過濾器。

格式化器

ConsoleHandler類和 FileHandler類可以生成文字和 XML 格式的日誌記錄。但是, 也可 以自定義格式。這需要擴充套件 Formatter 類並覆蓋下面這個方法:

String format(LogRecord record)

可以根據自己的願望對記錄中的資訊進行格式化,並返冋結果字串。在 format方法 中, 有可能會呼叫下面這個方法

String formatMessage(LogRecord record)

這個方法對記錄中的部分訊息進行格式化、 引數替換和本地化應用操作。

很多檔案格式(如 XML) 需要在已格式化的記錄的前後加上一個頭部和尾部。

日誌記錄說明

下面的“ 日誌說明書”總結了一些最常用的操作。

1 ) 為一個簡單的應用程式,選擇一個日誌記錄器,並把日誌記錄器命名為與主應用程 序包一樣的名字,例如,com.mycompany.myprog, 這是一種好的程式設計習慣。 另外,可以通過 呼叫下列方法得到日誌記錄器。

Logger logger = Logger.getLogger("com.mycompany.myprog");

2 ) 預設的日誌配置將級別等於或高於 INFO級別的所有訊息記錄到控制檯。使用者可以覆 蓋預設的配置檔案。但是正如前面所述,改變配置需要做相當多的工作。因此,最好在應用 程式中安裝一個更加適宜的預設配置。

3 ) 現在,可以記錄自己想要的內容了。但需要牢記: 所有級別為 INFO、 WARNING 和 SEVERE 的訊息都將顯示到控制檯上。 因此, 最好只將對程式使用者有意義的訊息設定為這幾 個級別。將程式設計師想要的日誌記錄,設定為 FINE 是一個很好的選擇。

程式清單 7-2 利用上述說明可實現:日誌記錄訊息也顯示在日誌視窗中。

//程式清單 7-2 logging/LogginglmageViewer.java
package logging;
​
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.logging.*;
import javax.swing.*;
​
/**
 * A modification of the image viewer program that logs various events.
 * @version 1.02 2007-05-31
 * @author Cay Horstmann
 */
public class LoggingImageViewer
{
   public static void main(String[] args)
   {
      if (System.getProperty("java.util.logging.config.class") == null
            && System.getProperty("java.util.logging.config.file") == null)
      {
         try
         {
            Logger.getLogger("com.horstmann.corejava").setLevel(Level.ALL);
            final int LOG_ROTATION_COUNT = 10;
            Handler handler = new FileHandler("%h/LoggingImageViewer.log", 0, LOG_ROTATION_COUNT);
            Logger.getLogger("com.horstmann.corejava").addHandler(handler);
         }
         catch (IOException e)
         {
            Logger.getLogger("com.horstmann.corejava").log(Level.SEVERE,
                  "Can't create log file handler", e);
         }
      }
​
      EventQueue.invokeLater(new Runnable()
         {
            public void run()
            {
               Handler windowHandler = new WindowHandler();
               windowHandler.setLevel(Level.ALL);
               Logger.getLogger("com.horstmann.corejava").addHandler(windowHandler);
​
               JFrame frame = new ImageViewerFrame();
               frame.setTitle("LoggingImageViewer");
               frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
​
               Logger.getLogger("com.horstmann.corejava").fine("Showing frame");
               frame.setVisible(true);
            }
         });
   }
}
​
/**
 * The frame that shows the image.
 */
class ImageViewerFrame extends JFrame
{
   private static final int DEFAULT_WIDTH = 300;
   private static final int DEFAULT_HEIGHT = 400;   
​
   private JLabel label;
   private static Logger logger = Logger.getLogger("com.horstmann.corejava");
​
   public ImageViewerFrame()
   {
      logger.entering("ImageViewerFrame", "<init>");      
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
​
      // set up menu bar
      JMenuBar menuBar = new JMenuBar();
      setJMenuBar(menuBar);
​
      JMenu menu = new JMenu("File");
      menuBar.add(menu);
​
      JMenuItem openItem = new JMenuItem("Open");
      menu.add(openItem);
      openItem.addActionListener(new FileOpenListener());
​
      JMenuItem exitItem = new JMenuItem("Exit");
      menu.add(exitItem);
      exitItem.addActionListener(new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               logger.fine("Exiting.");
               System.exit(0);
            }
         });
​
      // use a label to display the images
      label = new JLabel();
      add(label);
      logger.exiting("ImageViewerFrame", "<init>");
   }
​
   private class FileOpenListener implements ActionListener
   {
      public void actionPerformed(ActionEvent event)
      {
         logger.entering("ImageViewerFrame.FileOpenListener", "actionPerformed", event);
​
         // set up file chooser
         JFileChooser chooser = new JFileChooser();
         chooser.setCurrentDirectory(new File("."));
​
         // accept all files ending with .gif
         chooser.setFileFilter(new javax.swing.filechooser.FileFilter()
            {
               public boolean accept(File f)
               {
                  return f.getName().toLowerCase().endsWith(".gif") || f.isDirectory();
               }
​
               public String getDescription()
               {
                  return "GIF Images";
               }
            });
​
         // show file chooser dialog
         int r = chooser.showOpenDialog(ImageViewerFrame.this);
​
         // if image file accepted, set it as icon of the label
         if (r == JFileChooser.APPROVE_OPTION)
         {
            String name = chooser.getSelectedFile().getPath();
            logger.log(Level.FINE, "Reading file {0}", name);
            label.setIcon(new ImageIcon(name));
         }
         else logger.fine("File open dialog canceled.");
         logger.exiting("ImageViewerFrame.FileOpenListener", "actionPerformed");
      }
   }
}
​
/**
 * A handler for displaying log records in a window.
 */
class WindowHandler extends StreamHandler
{
   private JFrame frame;
​
   public WindowHandler()
   {
      frame = new JFrame();
      final JTextArea output = new JTextArea();
      output.setEditable(false);
      frame.setSize(200, 200);
      frame.add(new JScrollPane(output));
      frame.setFocusableWindowState(false);
      frame.setVisible(true);
      setOutputStream(new OutputStream()
         {
            public void write(int b)
            {
            } // not called
public void write(byte[] b, int off, int len)
            {
               output.append(new String(b, off, len));
            }
         });
   }
​
   public void publish(LogRecord record)
   {
      if (!frame.isVisible()) return;
      super.publish(record);
      flush();
   }
}