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//              try {
166//                      pbClmNo = table.getColumnNo( PAGEBREAK );
167//              }
168//              catch( HybsSystemException e ) {
169//                      pbClmNo = -1;
170//              }
171        }
172
173        /**
174         * 雛型ファイル名をセットします。
175         *
176         * @og.rev 3.6.0.0 (2004/09/17) メソッド名の変更。setInputFile ⇒ setTemplateFile
177         * @og.rev 3.8.0.0 (2005/06/07) 引数を String  ⇒ File に変更
178         *
179         * @param   inFile 雛型ファイル名
180         */
181        public void setTemplateFile( final File inFile ) {
182                templateFile = inFile;
183        }
184
185        /**
186         * 最初のページのみに使用する雛型ファイル名をセットします。
187         *
188         * @og.rev 3.6.0.0 (2004/09/17) 新規追加
189         * @og.rev 3.8.0.0 (2005/06/07) 引数を String  ⇒ File に変更
190         *
191         * @param   inFile 最初のページの雛型ファイル名
192         */
193        public void setFirstTemplateFile( final File inFile ) {
194                firstTemplateFile = inFile;
195        }
196
197        /**
198         * 変換後ファイルを出力するディレクトリ名をセットします。
199         * ディレクトリが存在しない場合は、新規に作成します。
200         *
201         * @og.rev 3.7.1.1 (2005/05/23) フォルダがない場合は、複数階層分のフォルダを自動で作成します。
202         *
203         * @param   outDir 出力ディレクトリ
204         */
205        public void setOutputDir( final String outDir ) {
206                htmlDir = outDir;
207
208                File dir = new File(htmlDir);
209                if( ! dir.exists() && ! dir.mkdirs() ) {
210                        String errMsg = "ディレクトリの作成に失敗しました。[" + htmlDir + "]";
211                        throw new HybsSystemException( errMsg );
212                }
213        }
214
215        /**
216         * 変換後ファイルキーをセットします。
217         * キーとは、拡張子の無い状態までのファイル名です。
218         * 変換後ファイルは、複数発生します。
219         * 実際に出力されるファイル名は、outFile + "_連番.html" となります。
220         *
221         * @param   outFile 出力ファイル名の共通部
222         */
223        public void setOutputFileKey( final String outFile ) {
224                htmlFileKey = outFile;
225        }
226
227        /**
228         * 帳票起動された要求番号をセットします。
229         *
230         * @og.rev 3.8.5.1 (2006/04/28) 新規追加
231         *
232         * @param   ykno 要求番号
233         */
234        public void setYkno( final String ykno ) {
235                this.ykno = ykno;
236        }
237
238        /**
239         * 固定部の key 部分を指定します。
240         * カンマで複数指定できます。
241         *
242         * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。
243         *
244         * @param   hKeys 固定部のキー
245         */
246        public void setHeaderKeys( final String[] hKeys ) {
247                if( hKeys != null ) {
248                        int size = hKeys.length ;
249                        headerKeys = new String[size];
250                        System.arraycopy( hKeys,0,headerKeys,0,size );
251                }
252                else {
253                        headerKeys = null;
254                }
255        }
256
257        /**
258         * 固定部のkey に対応する値を指定します。
259         * カンマで複数指定で、リクエスト情報でも設定できます。
260         *
261         * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。
262         *
263         * @param   hVals 固定部の値
264         */
265        public void setHeaderVals( final String[] hVals ) {
266                if( hVals != null ) {
267                        int size = hVals.length ;
268                        headerVals = new String[size];
269                        System.arraycopy( hVals,0,headerVals,0,size );
270                }
271                else {
272                        headerVals = null;
273                }
274        }
275
276        /**
277         * 雛型帳票に対する、実際の行番号を求めます。
278         * これは、雛型の複数回読みをサポートする為、実際の雛型のrow番号と
279         * DBTableModel から取得すべき row番号が、異なる為です。
280         * オーバーフロー時は、Exception を避ける為、-1 を返します。
281         *
282         * @og.rev 3.5.6.0 (2004/06/18) noDataflag の追加。
283         * @og.rev 3.5.6.3 (2004/07/12) noDataflag の廃止。
284         * @og.rev 3.6.0.4 (2004/10/14) FIRST 雛型時の対応追加。
285         * @og.rev 3.7.0.1 (2005/01/31) ページブレイク処理に対応。
286         * @og.rev 3.8.1.2 (2005/12/19) PAGE_END_CUT用にdataOverフラグを追加
287         *
288         * @param       row 固定部の値(オーバーフロー時は、-1 )
289         *
290         * @return      実際の行番号
291         */
292        protected int getRealRow( final int row ) {
293
294                // 3.7.0.1 (2005/01/31) ページブレイク処理に対応。
295                int realRow = pageRowCount + row + lineCopyCnt ;
296                if( maxRowCount <= realRow ) { maxRowCount = realRow + 1; }
297
298                if( realRow >= (tableSize-1) ) {                     // 行番号が最大値と同じ(データは存在)
299                        rowOver = true;
300                        if( realRow >= tableSize ) { // さらに、データは存在しない。
301                                realRow = -1;           // 3.5.6.3 (2004/07/12) オーバーフロー
302                                dataOver = true;        // 3.8.1.2 (2005/12/19)
303                        }
304                }
305
306                return realRow ;
307        }
308
309        /**
310         * 指定のキーについて、その値を取得します。
311         * 値の取得方法として、
312         *   {&#064;xxx_no} 形式の場合は、DBTableModel から、
313         *   {&#064;XXXX} 形式で、かつ、rowOver が false の場合は、ヘッダーから、
314         *   {&#064;XXXX} 形式で、かつ、rowOver が true の場合は、フッターから、
315         * 取得します。
316         * rowOver は、{&#064;xxx_no} 形式の番号欄(no)が、DBTableModel のデータ件数よりも
317         * 大きい場合に、セットされます。
318         *
319         * @og.rev 3.5.6.0 (2004/06/18) noDataflag の追加。
320         * @og.rev 3.5.6.3 (2004/07/12) noDataflag の廃止。
321         * @og.rev 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、子クラスから移動します。
322         * @og.rev 3.7.0.1 (2005/01/31) ページブレイク時の処理追加。
323         * @og.rev 3.7.0.2 (2005/02/18) HTML のエスケープ文字対応
324         * @og.rev 3.7.1.1 (2005/05/09) セル内の改行 <br> は、エスケープしない。
325         * @og.rev 3.8.0.0 (2005/06/07) Shift-JIS で中国語を扱う。(Unicodeエスケープ文字は、エスケープしない)
326         * @og.rev 3.8.5.1 (2006/04/28) YKNO を特別扱いする。
327         *
328         * @param   key 指定のキー
329         *
330         * @return  指定のキーの値
331         */
332        protected String getValue( final String key ) {
333                if( pageBreak ) { return ""; }          // 3.7.0.1 (2005/01/31) ページブレイク時の処理
334
335                int sp = key.lastIndexOf( '_' );
336                if( sp >= 0 ) {
337                        try {
338                                int row = Integer.parseInt( key.substring( sp+1 ) );
339                                int realRow = getRealRow( row );
340
341                                if( realRow >= 0 ) { // 3.5.6.3 (2004/07/12)
342                                        formatErr = false;      // 3.6.0.0 (2004/09/24)
343                                        int col = table.getColumnNo( key.substring( 0,sp ),false );
344                                        if( col < 0 ) {
345                                                // 超暫定対策:I 変数で、行番号を出力する。
346                                                if( "I".equals( key.substring( 0,sp ) ) ) {
347                                                        return String.valueOf( realRow+1 );             // 行番号は物理行+1
348                                                }
349                                                else {
350                                                        String errMsg = "カラム名が存在しません:[" + key + "]" ;
351                                                        System.out.println( errMsg );
352                                                        LogWriter.log( errMsg );
353                                                        return "" ;
354                                                }
355                                        }
356
357                                        String val = table.getValue( realRow,col );
358
359                                        // 3.7.0.1 (2005/01/31) ページブレイク時の処理追加
360                                        if( pbClmNo == col ) {
361                                                if( ! rowOver ) {
362                                                        String val2 = table.getValue( realRow+1,pbClmNo );      // 先読み
363                                                        if( val != null && ! val.equals( val2 ) ) {
364                                                                pageBreak = true;
365                                                        }
366                                                }
367                                                return "";      // ページブレイクカラムは、すべて""に変換する。
368                                        }
369                                        // 3.7.1.1 (2005/05/09) セル内の改行 <br> は、エスケープしない。
370                                        val = StringUtil.htmlFilter( val );
371                                        val = StringUtil.replace( val,"&lt;br&gt;","<br>" );
372                                        // 3.8.0.0 (2005/06/07) Shift-JIS で中国語を扱う。(Unicodeエスケープ文字は、エスケープしない)
373                                        val = StringUtil.replace( val,"&amp;#","&#" );  // 中国語変換対応 &amp;# は変換しない
374                                        return table.getDBColumn( col ).getRendererValue( val );
375                                }
376                        }
377                        catch ( NumberFormatException ex ) {    // 4.0.0 (2005/01/31)
378                                String errMsg = "警告:ヘッダーに'_'カラム名が使用  "
379                                                        + "key=[" + key + "]  "
380                                                        + ex.getMessage() ;
381                                LogWriter.log( errMsg );
382                                // フォーマットエラーは、何もしない。
383                                // 通常のカラム名にアンダーバーが使用されている可能性があるため。
384                        }
385                        catch ( RuntimeException ex ) {
386                                String errMsg = "カラムデータ取得処理で、エラーが発生しました。  "
387                                                        + "key=[" + key + "]  "
388                                                        + ex.getMessage() ;
389                                LogWriter.log( errMsg );
390                                // フォーマットエラーは、何もしない。
391                        }
392                }
393
394                // 3.8.5.1 (2006/04/28) YKNO を特別扱いする。
395                if( "YKNO".equals( key ) ) { return ykno; }
396
397                String rtnVal ;
398                if( rowOver ) { rtnVal = getFooterValue( key ); }
399                else          { rtnVal = getHeaderValue( key ); }
400
401                if( rtnVal == null ) { rtnVal = ""; }
402                return rtnVal ;
403        }
404
405        /**
406         * 固定部のkey に対応する値を取得します。
407         *
408         * @param   key String
409         *
410         * @return   固定部の値
411         */
412        private String getHeaderValue( final String key ) {
413                if( headerKeys == null ||
414                        headerVals == null ||
415                        key        == null ) { return null; }
416
417                for( int i=0; i<headerKeys.length; i++ ) {
418                        if( key.equals( headerKeys[i] ) ) { return headerVals[i]; }
419                }
420                return null;
421        }
422
423        /**
424         * 繰り返し部の終了後に表示する key 部分を指定します。
425         * カンマで複数指定できます。
426         *
427         * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。
428         *
429         * @param   fKeys 繰り返し部の終了後に表示する key
430         */
431        public void setFooterKeys( final String[] fKeys ) {
432                if( fKeys != null ) {
433                        int size = fKeys.length ;
434                        footerKeys = new String[size];
435                        System.arraycopy( fKeys,0,footerKeys,0,size );
436                }
437                else {
438                        footerKeys = null;
439                }
440        }
441
442        /**
443         * 繰り返し部の終了後に表示する key 部分を取得します。
444         *
445         * @param   key String
446         *
447         * @return   繰り返し部の終了後に表示する key
448         */
449        private String getFooterValue( final String key ) {
450                if( footerKeys == null ||
451                        footerVals == null ||
452                        key        == null ) { return null; }
453
454                for( int i=0; i<footerKeys.length; i++ ) {
455                        if( key.equals( footerKeys[i] ) ) { return footerVals[i]; }
456                }
457                return null;
458        }
459
460        /**
461         * 固定部のkey に対応する値を指定します。
462         * カンマで複数指定で、リクエスト情報でも設定できます。
463         *
464         * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。
465         *
466         * @param   fVals 繰り返し部の終了後に表示する値
467         */
468        public void setFooterVals( final String[] fVals ) {
469                if( fVals != null ) {
470                        int size = fVals.length ;
471                        footerVals = new String[size];
472                        System.arraycopy( fVals,0,footerVals,0,size );
473                }
474                else {
475                        footerVals = null;
476                }
477        }
478
479        /**
480         * ボディー部(繰り返し部)がなくなったときに、それ以降を表示するかどうかを指定します。
481         * true では、それ以降を出力しません。
482         * デフォルト "true" (なくなった時点で、出力しない。)です。
483         *
484         * @param   pageEndCut 繰り返し部の終了後に継続処理するかどうか (true:処理しない/false:処理する)
485         */
486        public void setPageEndCut( final boolean pageEndCut ) {
487                this.pageEndCut = pageEndCut ;
488        }
489
490        /**
491         * BufferedReader を、初期化します。
492         * これは、雛型ファイルの終端まで読取り、処理した場合、もう一度
493         * 初めから読み込みなおす処理を行います。
494         * 基本的に、書き込みも初期化する必要があります。
495         *
496         * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。
497         *
498         * @og.rev 3.1.3.0 (2003/04/10) "DEFAULT" エンコーディング名のサポートを廃止。
499         * @og.rev 3.5.5.9 (2004/06/07) FileUtil.getBufferedReader を使用
500         * @og.rev 3.6.0.0 (2004/09/17) 最初のページのみに使用する雛型ファイル名を追加します。
501         * @og.rev 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、子クラスから移動します。
502         *
503         */
504        protected void initReader() {
505                Closer.ioClose( reader );               // 4.0.0 (2006/01/31) close 処理時の IOException を無視
506
507                if( reader == null && firstTemplateFile != null ) {
508                        reader = FileUtil.getBufferedReader(firstTemplateFile,ENCODE);
509                }
510                else {
511                        if( formatErr ) {
512                                String errMsg = "Error in HTML File. " + HybsSystem.CR
513                                                                + "Excel containing two or more sheets is not supporting."
514                                                                + HybsSystem.CR
515                                                                + "or HTML template File is not in '{@xxxx_0}' key word." ;
516                                throw new HybsSystemException( errMsg );
517                        }
518                        reader = FileUtil.getBufferedReader(templateFile,ENCODE);
519                        formatErr = true;               // 初期化します。クリアしなければエラー
520                }
521        }
522
523        /**
524         * PrintWriter を、初期化します。
525         * これは、雛型ファイルを終端まで読取り、処理した場合、出力ファイル名を
526         * 変えて、別ファイルとして出力する為のものです。
527         * 基本的に、読取も初期化する必要があります。
528         *
529         * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。
530         *
531         * @og.rev 3.0.0.1 (2003/02/14) ページの最大ページ数の制限を追加。暴走停止用
532         * @og.rev 3.1.3.0 (2003/04/10) "DEFAULT" エンコーディング名のサポートを廃止。
533         * @og.rev 3.5.5.9 (2004/06/07) FileUtil.getPrintWriter メソッドを使用
534         * @og.rev 3.7.0.1 (2005/01/31) ページブレイク処理に対応。
535         * @og.rev 3.8.0.0 (2005/06/07) FileUtil#getPrintWriter を利用。
536         * @og.rev 3.8.5.3 (2006/06/30) EXCEL最大シート数のエラーメッセージを変更。
537         *
538         */
539        protected void initWriter() {
540                if( writer != null ) {
541                        writer.flush();
542                        writer.close();
543                        writer = null;
544                        pageCount++ ;
545                        if( pageCount >= maxPageCount ) {
546                                String errMsg = "EXCELのページ(シート)が最大ページ数(1000)をオーバーしました。"
547                                                                + HybsSystem.CR
548                                                                + "この数は、DB_MAX_ROW_COUNT ではなく、LISTID_999.htmlのオーバーを意味します。"
549                                                                + HybsSystem.CR;
550                                throw new HybsSystemException( errMsg );
551                        }
552                }
553
554                int pgCnt = pageCount + 1000;           // 桁合わせの為、下3桁を利用します。
555
556                String subName = String.valueOf( pgCnt ).substring( 1 );
557                String filename = htmlFileKey + "_" + subName + ".html" ;
558
559                // 3.8.0.0 (2005/06/07) FileUtil#getPrintWriter を利用。
560                writer = FileUtil.getPrintWriter( new File( htmlDir,filename ),ENCODE );
561
562                // 3.7.0.1 (2005/01/31) ページブレイク時の処理
563                pageRowCount = maxRowCount ;    // そのページの頭のデータ行数をセット
564                pageBreak    = false;                   // pageBreak フラグを元に戻す。
565        }
566
567        /**
568         * ヘッダーフッターのレンデラーデータを設定します。
569         * カンマで複数指定で、リクエスト情報でも設定できます。
570         *
571         */
572        protected void setHeaderFooter() {
573
574                DBColumn clm ;
575                if( headerKeys != null ) {
576                        for( int i=0; i<headerKeys.length; i++ ) {
577                                clm = resource.getDBColumn( headerKeys[i] );
578                                if( clm != null ) {
579                                        headerVals[i] = clm.getRendererValue( headerVals[i] );
580                                }
581                        }
582                }
583
584                if( footerKeys != null ) {
585                        for( int i=0; i<footerKeys.length; i++ ) {
586                                clm = resource.getDBColumn( footerKeys[i] );
587                                if( clm != null ) {
588                                        footerVals[i] = clm.getRendererValue( footerVals[i] );
589                                }
590                        }
591                }
592        }
593
594        /**
595         * リーダー、ライターの終了処理を行います。
596         *
597         */
598        private void close() {
599                if( writer != null ) {
600                        writer.flush();
601                        writer.close();
602                        writer = null;
603                }
604                Closer.ioClose( reader );               // 4.0.0 (2006/01/31) close 処理時の IOException を無視
605                reader = null;
606        }
607}