001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.hayabusa.report;
017
018import org.opengion.hayabusa.common.HybsSystem;
019import org.opengion.hayabusa.common.HybsSystemException;
020import org.opengion.fukurou.util.LogWriter;
021import org.opengion.hayabusa.db.DBTableModel;
022import org.opengion.hayabusa.db.DBColumn;
023import org.opengion.hayabusa.resource.ResourceManager;
024import org.opengion.fukurou.util.StringUtil;
025import org.opengion.fukurou.util.FileUtil;
026import org.opengion.fukurou.util.Closer ;
027
028import java.io.File;
029import java.io.BufferedReader;
030import java.io.PrintWriter;
031
032/**
033 * DBTableReport インターフェース のデフォルト実装クラスです。
034 * writeReport() を、オーバーライドすれば,各種出力フォーマットに合わせた
035 * サブクラスを実現する事が可能です。
036 *
037 * @og.group 帳票システム
038 *
039 * @version  4.0
040 * @author       Kazuhiko Hasegawa
041 * @since    JDK5.0,
042 */
043public abstract class AbstractDBTableReport implements DBTableReport {
044        private static final String ENCODE = HybsSystem.REPORT_ENCODE ;
045
046        protected String[]              headerKeys      = null;         // 固定部の key 部分を指定する。カンマで複数指定できる。
047        protected String[]              headerVals      = null;         // 固定部の key に対応する値を指定する。
048        protected String[]              footerKeys      = null;         // 繰り返し部の終了後に表示する key 部分を指定する。カンマで複数指定できる。
049        protected String[]              footerVals      = null;         // 繰り返し部の終了後に表示する key に対する値を指定する。
050        protected boolean               pageEndCut      = false;        // ボディー部(繰り返し部)がなくなったときに、それ以降のページを出力するか指定する。
051        protected int                   maxRowCount     = 0;            // 自動計算方式を採用
052        protected int                   pageRowCount    = 0;    // 過去のページの最大件数。 3.7.0.1 (2005/01/31)
053        protected int                   lineCopyCnt     = 0;            // LINE_COPY した際の加算行番号。 4.0.0 (2007/06/08)
054        protected ResourceManager resource  = null;             // 4.0.0 (2005/01/31)
055        protected PrintWriter   writer          = null;
056        protected BufferedReader reader         = null;
057        protected File                  templateFile            = null;         // 3.8.0.0 (2005/06/07)
058        protected File                  firstTemplateFile       = null;         // 3.8.0.0 (2005/06/07)
059        protected String                htmlDir         = null;
060        protected String                htmlFileKey     = null;
061        protected String                ykno            = null;         // 3.8.5.1 (2006/04/28) 追加
062        protected DBTableModel  table           = null;
063
064        protected int                   pageCount       = 0;
065        protected int                   maxPageCount = 1000;
066        protected boolean               rowOver         = false;        // データ件数分のカラムを要求されると、true にセットされる。
067        protected boolean               dataOver        = false;        // 3.8.1.2 (2005/12/19) データがなくなると、true にセットされる。
068
069        // 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、子クラスから移動します。
070        private boolean  formatErr      = false;                // フォーマットエラーの判定
071
072        // 3.6.1.0 (2005/01/05) 帳票ID追加
073        protected String                listId          = null;
074
075        // 3.7.0.1 (2005/01/31) ページブレイク時の処理
076        private static final String PAGEBREAK = "PAGEBREAK";
077        private int                             pbClmNo         = -1;           // PAGEBREAK カラムの番号
078        private boolean                 pageBreak       = false;        // PAGEBREAK = "1" が見つかった時、true
079        private int                             tableSize       = 0;
080
081        /**
082         * DBTableModel から データを作成して,PrintWriter に書き出します。
083         *
084         */
085        public void writeReport() {
086                setHeaderFooter();
087                initReader();
088                initWriter();
089                String str ;
090                while( (str = readLine()) != null ) {
091                        println( changeData( str ) );
092                }
093                close();
094        }
095
096        /**
097         * 入力文字列 を読み取って、出力します。
098         * tr タグを目印に、1行(trタグ間)ずつ取り出します。
099         * 読み取りを終了する場合は、null を返します。
100         * 各サブクラスで実装してください。
101         *
102         * @return      出力文字列
103         */
104        abstract protected String readLine() ;
105
106        /**
107         * 入力文字列 を加工して、出力します。
108         * {@XXXX} をテーブルモデルより読み取り、値をセットします。
109         * 各サブクラスで実装してください。
110         *
111         * @param       inLine  入力文字列
112         *
113         * @return      出力文字列
114         */
115        abstract protected String changeData( final String inLine ) ;
116
117        /**
118         * 入力文字列 を読み取って、出力します。
119         * 各サブクラスで実装してください。
120         *
121         * @param line 出力文字列
122         */
123        abstract protected void println( final String line ) ;
124
125        /**
126         * リソースマネージャーをセットします。
127         * これは、言語(ロケール)に応じた DBColumn をあらかじめ設定しておく為に
128         * 必要です。
129         * リソースマネージャーが設定されていない、または、所定のキーの DBColumn が
130         * リソースに存在しない場合は、内部で DBColumn オブジェクトを作成します。
131         *
132         * @og.rev 4.0.0.0 (2005/01/31) lang ⇒ ResourceManager へ変更
133         *
134         * @param  resource リソースマネージャー
135         */
136        public void setResourceManager( final ResourceManager resource ) {
137                this.resource = resource;
138        }
139
140        /**
141         * 帳票ID をセットします。
142         * この帳票IDを利用して、画像ファイル等のセーブディレクトリを求めます。
143         *
144         * @og.rev 3.6.1.0 (2005/01/05) 新規作成
145         *
146         * @param   listId 帳票ID
147         */
148        public void setListId( final String listId ) {
149                this.listId = listId ;
150        }
151
152        /**
153         * DBTableModel をセットします。
154         *
155         * @og.rev 3.7.0.1 (2005/01/31) ページブレイク時の処理
156         *
157         * @param       table   DBTableModelオブジェクト
158         */
159        public void setDBTableModel( final DBTableModel table ) {
160                this.table = table;
161                // 3.7.0.1 (2005/01/31) ページブレイク時の処理
162                tableSize = table.getRowCount();
163                pbClmNo   = table.getColumnNo( PAGEBREAK,false );               // 存在しない場合は、-1
164        }
165
166        /**
167         * 雛型ファイル名をセットします。
168         *
169         * @og.rev 3.6.0.0 (2004/09/17) メソッド名の変更。setInputFile ⇒ setTemplateFile
170         * @og.rev 3.8.0.0 (2005/06/07) 引数を String  ⇒ File に変更
171         *
172         * @param   inFile 雛型ファイル名
173         */
174        public void setTemplateFile( final File inFile ) {
175                templateFile = inFile;
176        }
177
178        /**
179         * 最初のページのみに使用する雛型ファイル名をセットします。
180         *
181         * @og.rev 3.6.0.0 (2004/09/17) 新規追加
182         * @og.rev 3.8.0.0 (2005/06/07) 引数を String  ⇒ File に変更
183         *
184         * @param   inFile 最初のページの雛型ファイル名
185         */
186        public void setFirstTemplateFile( final File inFile ) {
187                firstTemplateFile = inFile;
188        }
189
190        /**
191         * 変換後ファイルを出力するディレクトリ名をセットします。
192         * ディレクトリが存在しない場合は、新規に作成します。
193         *
194         * @og.rev 3.7.1.1 (2005/05/23) フォルダがない場合は、複数階層分のフォルダを自動で作成します。
195         *
196         * @param   outDir 出力ディレクトリ
197         */
198        public void setOutputDir( final String outDir ) {
199                htmlDir = outDir;
200
201                File dir = new File(htmlDir);
202                if( ! dir.exists() && ! dir.mkdirs() ) {
203                        String errMsg = "ディレクトリの作成に失敗しました。[" + htmlDir + "]";
204                        throw new HybsSystemException( errMsg );
205                }
206        }
207
208        /**
209         * 変換後ファイルキーをセットします。
210         * キーとは、拡張子の無い状態までのファイル名です。
211         * 変換後ファイルは、複数発生します。
212         * 実際に出力されるファイル名は、outFile + "_連番.html" となります。
213         *
214         * @param   outFile 出力ファイル名の共通部
215         */
216        public void setOutputFileKey( final String outFile ) {
217                htmlFileKey = outFile;
218        }
219
220        /**
221         * 帳票起動された要求番号をセットします。
222         *
223         * @og.rev 3.8.5.1 (2006/04/28) 新規追加
224         *
225         * @param   ykno 要求番号
226         */
227        public void setYkno( final String ykno ) {
228                this.ykno = ykno;
229        }
230
231        /**
232         * 固定部の key 部分を指定します。
233         * カンマで複数指定できます。
234         *
235         * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。
236         *
237         * @param   hKeys 固定部のキー
238         */
239        public void setHeaderKeys( final String[] hKeys ) {
240                if( hKeys != null ) {
241                        int size = hKeys.length ;
242                        headerKeys = new String[size];
243                        System.arraycopy( hKeys,0,headerKeys,0,size );
244                }
245                else {
246                        headerKeys = null;
247                }
248        }
249
250        /**
251         * 固定部のkey に対応する値を指定します。
252         * カンマで複数指定で、リクエスト情報でも設定できます。
253         *
254         * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。
255         *
256         * @param   hVals 固定部の値
257         */
258        public void setHeaderVals( final String[] hVals ) {
259                if( hVals != null ) {
260                        int size = hVals.length ;
261                        headerVals = new String[size];
262                        System.arraycopy( hVals,0,headerVals,0,size );
263                }
264                else {
265                        headerVals = null;
266                }
267        }
268
269        /**
270         * 雛型帳票に対する、実際の行番号を求めます。
271         * これは、雛型の複数回読みをサポートする為、実際の雛型のrow番号と
272         * DBTableModel から取得すべき row番号が、異なる為です。
273         * オーバーフロー時は、Exception を避ける為、-1 を返します。
274         *
275         * @og.rev 3.5.6.0 (2004/06/18) noDataflag の追加。
276         * @og.rev 3.5.6.3 (2004/07/12) noDataflag の廃止。
277         * @og.rev 3.6.0.4 (2004/10/14) FIRST 雛型時の対応追加。
278         * @og.rev 3.7.0.1 (2005/01/31) ページブレイク処理に対応。
279         * @og.rev 3.8.1.2 (2005/12/19) PAGE_END_CUT用にdataOverフラグを追加
280         *
281         * @param       row 固定部の値(オーバーフロー時は、-1 )
282         *
283         * @return      実際の行番号
284         */
285        protected int getRealRow( final int row ) {
286
287                // 3.7.0.1 (2005/01/31) ページブレイク処理に対応。
288                int realRow = pageRowCount + row + lineCopyCnt ;
289                if( maxRowCount <= realRow ) { maxRowCount = realRow + 1; }
290
291                if( realRow >= (tableSize-1) ) {                        // 行番号が最大値と同じ(データは存在)
292                        rowOver = true;
293                        if( realRow >= tableSize ) {    // さらに、データは存在しない。
294                                realRow = -1;           // 3.5.6.3 (2004/07/12) オーバーフロー
295                                dataOver = true;        // 3.8.1.2 (2005/12/19)
296                        }
297                }
298
299                return realRow ;
300        }
301
302        /**
303         * 指定のキーについて、その値を取得します。
304         * 値の取得方法として、
305         *   {&#064;xxx_no} 形式の場合は、DBTableModel から、
306         *   {&#064;XXXX} 形式で、かつ、rowOver が false の場合は、ヘッダーから、
307         *   {&#064;XXXX} 形式で、かつ、rowOver が true の場合は、フッターから、
308         * 取得します。
309         * rowOver は、{&#064;xxx_no} 形式の番号欄(no)が、DBTableModel のデータ件数よりも
310         * 大きい場合に、セットされます。
311         *
312         * @og.rev 3.5.6.0 (2004/06/18) noDataflag の追加。
313         * @og.rev 3.5.6.3 (2004/07/12) noDataflag の廃止。
314         * @og.rev 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、子クラスから移動します。
315         * @og.rev 3.7.0.1 (2005/01/31) ページブレイク時の処理追加。
316         * @og.rev 3.7.0.2 (2005/02/18) HTML のエスケープ文字対応
317         * @og.rev 3.7.1.1 (2005/05/09) セル内の改行 <br> は、エスケープしない。
318         * @og.rev 3.8.0.0 (2005/06/07) Shift-JIS で中国語を扱う。(Unicodeエスケープ文字は、エスケープしない)
319         * @og.rev 3.8.5.1 (2006/04/28) YKNO を特別扱いする。
320         *
321         * @param   key 指定のキー
322         *
323         * @return  指定のキーの値
324         */
325        protected String getValue( final String key ) {
326                if( pageBreak ) { return ""; }          // 3.7.0.1 (2005/01/31) ページブレイク時の処理
327
328                int sp = key.lastIndexOf( '_' );
329                if( sp >= 0 ) {
330                        try {
331                                int row = Integer.parseInt( key.substring( sp+1 ) );
332                                int realRow = getRealRow( row );
333
334                                if( realRow >= 0 ) {    // 3.5.6.3 (2004/07/12)
335                                        formatErr = false;      // 3.6.0.0 (2004/09/24)
336                                        int col = table.getColumnNo( key.substring( 0,sp ),false );
337                                        if( col < 0 ) {
338                                                // 超暫定対策:I 変数で、行番号を出力する。
339                                                if( "I".equals( key.substring( 0,sp ) ) ) {
340                                                        return String.valueOf( realRow+1 );             // 行番号は物理行+1
341                                                }
342                                                else {
343                                                        String errMsg = "カラム名が存在しません:[" + key + "]" ;
344                                                        System.out.println( errMsg );
345                                                        LogWriter.log( errMsg );
346                                                        return "" ;
347                                                }
348                                        }
349
350                                        String val = table.getValue( realRow,col );
351
352                                        // 3.7.0.1 (2005/01/31) ページブレイク時の処理追加
353                                        if( pbClmNo == col ) {
354                                                if( ! rowOver ) {
355                                                        String val2 = table.getValue( realRow+1,pbClmNo );      // 先読み
356                                                        if( val != null && ! val.equals( val2 ) ) {
357                                                                pageBreak = true;
358                                                        }
359                                                }
360                                                return "";      // ページブレイクカラムは、すべて""に変換する。
361                                        }
362                                        // 3.7.1.1 (2005/05/09) セル内の改行 <br> は、エスケープしない。
363                                        val = StringUtil.htmlFilter( val );
364                                        val = StringUtil.replace( val,"&lt;br&gt;","<br>" );
365                                        // 3.8.0.0 (2005/06/07) Shift-JIS で中国語を扱う。(Unicodeエスケープ文字は、エスケープしない)
366                                        val = StringUtil.replace( val,"&amp;#","&#" );  // 中国語変換対応 &amp;# は変換しない
367                                        return table.getDBColumn( col ).getRendererValue( val );
368                                }
369                        }
370                        catch ( NumberFormatException ex ) {    // 4.0.0 (2005/01/31)
371                                String errMsg = "警告:ヘッダーに'_'カラム名が使用  "
372                                                        + "key=[" + key + "]  "
373                                                        + ex.getMessage() ;
374                                LogWriter.log( errMsg );
375                                // フォーマットエラーは、何もしない。
376                                // 通常のカラム名にアンダーバーが使用されている可能性があるため。
377                        }
378                        catch ( RuntimeException ex ) {
379                                String errMsg = "カラムデータ取得処理で、エラーが発生しました。  "
380                                                        + "key=[" + key + "]  "
381                                                        + ex.getMessage() ;
382                                LogWriter.log( errMsg );
383                                // フォーマットエラーは、何もしない。
384                        }
385                }
386
387                // 3.8.5.1 (2006/04/28) YKNO を特別扱いする。
388                if( "YKNO".equals( key ) ) { return ykno; }
389
390                String rtnVal ;
391                if( rowOver ) { rtnVal = getFooterValue( key ); }
392                else          { rtnVal = getHeaderValue( key ); }
393
394                if( rtnVal == null ) { rtnVal = ""; }
395                return rtnVal ;
396        }
397
398        /**
399         * 固定部のkey に対応する値を取得します。
400         *
401         * @param   key String
402         *
403         * @return   固定部の値
404         */
405        private String getHeaderValue( final String key ) {
406                if( headerKeys == null ||
407                        headerVals == null ||
408                        key        == null ) { return null; }
409
410                for( int i=0; i<headerKeys.length; i++ ) {
411                        if( key.equals( headerKeys[i] ) ) { return headerVals[i]; }
412                }
413                return null;
414        }
415
416        /**
417         * 繰り返し部の終了後に表示する key 部分を指定します。
418         * カンマで複数指定できます。
419         *
420         * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。
421         *
422         * @param   fKeys 繰り返し部の終了後に表示する key
423         */
424        public void setFooterKeys( final String[] fKeys ) {
425                if( fKeys != null ) {
426                        int size = fKeys.length ;
427                        footerKeys = new String[size];
428                        System.arraycopy( fKeys,0,footerKeys,0,size );
429                }
430                else {
431                        footerKeys = null;
432                }
433        }
434
435        /**
436         * 繰り返し部の終了後に表示する key 部分を取得します。
437         *
438         * @param   key String
439         *
440         * @return   繰り返し部の終了後に表示する key
441         */
442        private String getFooterValue( final String key ) {
443                if( footerKeys == null ||
444                        footerVals == null ||
445                        key        == null ) { return null; }
446
447                for( int i=0; i<footerKeys.length; i++ ) {
448                        if( key.equals( footerKeys[i] ) ) { return footerVals[i]; }
449                }
450                return null;
451        }
452
453        /**
454         * 固定部のkey に対応する値を指定します。
455         * カンマで複数指定で、リクエスト情報でも設定できます。
456         *
457         * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。
458         *
459         * @param   fVals 繰り返し部の終了後に表示する値
460         */
461        public void setFooterVals( final String[] fVals ) {
462                if( fVals != null ) {
463                        int size = fVals.length ;
464                        footerVals = new String[size];
465                        System.arraycopy( fVals,0,footerVals,0,size );
466                }
467                else {
468                        footerVals = null;
469                }
470        }
471
472        /**
473         * ボディー部(繰り返し部)がなくなったときに、それ以降を表示するかどうかを指定します。
474         * true では、それ以降を出力しません。
475         * デフォルト "true" (なくなった時点で、出力しない。)です。
476         *
477         * @param   pageEndCut 繰り返し部の終了後に継続処理するかどうか (true:処理しない/false:処理する)
478         */
479        public void setPageEndCut( final boolean pageEndCut ) {
480                this.pageEndCut = pageEndCut ;
481        }
482
483        /**
484         * BufferedReader を、初期化します。
485         * これは、雛型ファイルの終端まで読取り、処理した場合、もう一度
486         * 初めから読み込みなおす処理を行います。
487         * 基本的に、書き込みも初期化する必要があります。
488         *
489         * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。
490         *
491         * @og.rev 3.1.3.0 (2003/04/10) "DEFAULT" エンコーディング名のサポートを廃止。
492         * @og.rev 3.5.5.9 (2004/06/07) FileUtil.getBufferedReader を使用
493         * @og.rev 3.6.0.0 (2004/09/17) 最初のページのみに使用する雛型ファイル名を追加します。
494         * @og.rev 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、子クラスから移動します。
495         *
496         */
497        protected void initReader() {
498                Closer.ioClose( reader );               // 4.0.0 (2006/01/31) close 処理時の IOException を無視
499
500                if( reader == null && firstTemplateFile != null ) {
501                        reader = FileUtil.getBufferedReader(firstTemplateFile,ENCODE);
502                }
503                else {
504                        if( formatErr ) {
505                                String errMsg = "Error in HTML File. " + HybsSystem.CR
506                                                                + "Excel containing two or more sheets is not supporting."
507                                                                + HybsSystem.CR
508                                                                + "or HTML template File is not in '{@xxxx_0}' key word." ;
509                                throw new HybsSystemException( errMsg );
510                        }
511                        reader = FileUtil.getBufferedReader(templateFile,ENCODE);
512                        formatErr = true;               // 初期化します。クリアしなければエラー
513                }
514        }
515
516        /**
517         * PrintWriter を、初期化します。
518         * これは、雛型ファイルを終端まで読取り、処理した場合、出力ファイル名を
519         * 変えて、別ファイルとして出力する為のものです。
520         * 基本的に、読取も初期化する必要があります。
521         *
522         * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。
523         *
524         * @og.rev 3.0.0.1 (2003/02/14) ページの最大ページ数の制限を追加。暴走停止用
525         * @og.rev 3.1.3.0 (2003/04/10) "DEFAULT" エンコーディング名のサポートを廃止。
526         * @og.rev 3.5.5.9 (2004/06/07) FileUtil.getPrintWriter メソッドを使用
527         * @og.rev 3.7.0.1 (2005/01/31) ページブレイク処理に対応。
528         * @og.rev 3.8.0.0 (2005/06/07) FileUtil#getPrintWriter を利用。
529         * @og.rev 3.8.5.3 (2006/06/30) EXCEL最大シート数のエラーメッセージを変更。
530         *
531         */
532        protected void initWriter() {
533                if( writer != null ) {
534                        writer.flush();
535                        writer.close();
536                        writer = null;
537                        pageCount++ ;
538                        if( pageCount >= maxPageCount ) {
539                                String errMsg = "EXCELのページ(シート)が最大ページ数(1000)をオーバーしました。"
540                                                                + HybsSystem.CR
541                                                                + "この数は、DB_MAX_ROW_COUNT ではなく、LISTID_999.htmlのオーバーを意味します。"
542                                                                + HybsSystem.CR;
543                                throw new HybsSystemException( errMsg );
544                        }
545                }
546
547                int pgCnt = pageCount + 1000;           // 桁合わせの為、下3桁を利用します。
548
549                String subName = String.valueOf( pgCnt ).substring( 1 );
550                String filename = htmlFileKey + "_" + subName + ".html" ;
551
552                // 3.8.0.0 (2005/06/07) FileUtil#getPrintWriter を利用。
553                writer = FileUtil.getPrintWriter( new File( htmlDir,filename ),ENCODE );
554
555                // 3.7.0.1 (2005/01/31) ページブレイク時の処理
556                pageRowCount = maxRowCount ;    // そのページの頭のデータ行数をセット
557                pageBreak    = false;                   // pageBreak フラグを元に戻す。
558        }
559
560        /**
561         * ヘッダーフッターのレンデラーデータを設定します。
562         * カンマで複数指定で、リクエスト情報でも設定できます。
563         *
564         */
565        protected void setHeaderFooter() {
566
567                DBColumn clm ;
568                if( headerKeys != null ) {
569                        for( int i=0; i<headerKeys.length; i++ ) {
570                                clm = resource.getDBColumn( headerKeys[i] );
571                                if( clm != null ) {
572                                        headerVals[i] = clm.getRendererValue( headerVals[i] );
573                                }
574                        }
575                }
576
577                if( footerKeys != null ) {
578                        for( int i=0; i<footerKeys.length; i++ ) {
579                                clm = resource.getDBColumn( footerKeys[i] );
580                                if( clm != null ) {
581                                        footerVals[i] = clm.getRendererValue( footerVals[i] );
582                                }
583                        }
584                }
585        }
586
587        /**
588         * リーダー、ライターの終了処理を行います。
589         *
590         */
591        private void close() {
592                if( writer != null ) {
593                        writer.flush();
594                        writer.close();
595                        writer = null;
596                }
597                Closer.ioClose( reader );               // 4.0.0 (2006/01/31) close 処理時の IOException を無視
598                reader = null;
599        }
600}