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.io;
017
018import java.io.BufferedReader;
019import java.util.Map;
020
021import org.opengion.fukurou.util.CSVTokenizer;
022import org.opengion.fukurou.util.StringUtil;
023import org.opengion.hayabusa.common.HybsSystem;
024import org.opengion.hayabusa.db.DBColumn;
025import org.opengion.hayabusa.db.DBTableModel;
026import org.opengion.hayabusa.resource.ResourceManager;
027import org.opengion.hayabusa.resource.CodeData;
028
029/**
030 * 指定の区切り記号(初期値:タブ区切り)ファイルの読み取りクラスです。
031 *
032 * 名前,データの入力部のみオーバーライドすれば,各種入力フォーマットに合わせた
033 * サブクラスを実現する事が可能です。
034 *
035 * @og.group ファイル入力
036 *
037 * @version  4.0
038 * @author   Kazuhiko Hasegawa
039 * @since    JDK5.0,
040 */
041public abstract class AbstractTableReader implements TableReader {
042        //* このプログラムのVERSION文字列を設定します。   {@value} */
043        private static final String VERSION = "5.5.8.5 (2012/11/27)" ;
044
045        private String  separator       = TAB_SEPARATOR;                // 項目区切り文字
046        private ResourceManager resource = null;                        // 4.0.0 (2005/01/31)
047        private int             maxRowCount     = HybsSystem.sysInt( "DB_MAX_ROW_COUNT" ) ;
048
049        protected DBTableModel  table           = null;
050        protected DBColumn[]    dbColumn        = null;
051
052        // 3.5.4.5 (2004/01/23) カラム名の外部指定を出来る様にする。
053        protected String  columns       = null;  // 外部指定のカラム名 ( 4.3.4.7 (2009/01/22) protectedに変更 )
054        private String    encode        = null;
055        private boolean   useNumber     = true;                 // 3.7.0.5 (2005/04/11)
056
057        private int               skipRowCount  = 0;            // 5.1.6.0 (2010/05/01) データの読み飛ばし設定
058        private boolean   useRenderer   = false;        // 5.2.1.0 (2010/10/01)
059
060        // 5.2.1.0 (2010/10/01) コードリソース毎のラベル逆引きマップ
061        private Map<?,?>[] maps = null;                         // 5.5.1.7 (2012/04/16) ワイルドカードを指定
062
063        private boolean   useDebug      = false;                // 5.5.7.2 (2012/10/09) デバッグ情報の出力するかどうか
064
065        /**
066         * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。
067         * コメント/空行を除き、最初の行は、必ず項目名が必要です。
068         * それ以降は、コメント/空行を除き、データとして読み込んでいきます。
069         * このメソッドは、EXCEL 読み込み時に使用します。
070         *
071         * @see #isExcel()
072         */
073        abstract public void readDBTable();
074
075        /**
076         * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。
077         * コメント/空行を除き、最初の行は、必ず項目名が必要です。
078         * それ以降は、コメント/空行を除き、データとして読み込んでいきます。
079         *
080         * @param   reader BufferedReaderオブジェクト
081         */
082        abstract public void readDBTable( final BufferedReader reader );
083
084        /**
085         * リソースマネージャーをセットします。
086         * これは、言語(ロケール)に応じた DBColumn をあらかじめ設定しておく為に
087         * 必要です。
088         * リソースマネージャーが設定されていない、または、所定のキーの DBColumn が
089         * リソースに存在しない場合は、内部で DBColumn オブジェクトを作成します。
090         *
091         * @og.rev 4.0.0.0 (2005/01/31) lang ⇒ ResourceManager へ変更
092         *
093         * @param  resource リソースマネージャー
094         */
095        public void setResourceManager( final ResourceManager resource ) {
096                this.resource = resource;
097        }
098
099        /**
100         * DBColumn オブジェクトをDBTable に設定します。
101         *
102         * @og.rev 3.5.4.2 (2003/12/15) private を protected に変更。
103         * @og.rev 3.5.4.5 (2004/01/23) DBColumn 配列に値をセットします。
104         * @og.rev 5.2.1.0 (2010/10/01) useRenderer対応(コードリソース毎のラベル逆引き)
105         *
106         * @param names カラム名配列
107         */
108        protected void setTableDBColumn( final String[] names ) {
109                dbColumn = new DBColumn[names.length] ;  // 3.5.4.5 追加
110                for( int i=0; i<names.length; i++ ) {
111                        DBColumn clm = resource.makeDBColumn( names[i] );
112                        table.setDBColumn( i,clm );
113                        dbColumn[i] = clm;                // 3.5.4.5 追加
114                }
115
116                if( useRenderer ) {
117                        maps = new Map<?,?>[names.length];              // 5.5.1.7 (2012/04/16) ワイルドカードを指定
118                        for( int i=0; i<names.length; i++ ) {
119                                CodeData cd = dbColumn[i].getCodeData();
120                                if( cd != null ) { maps[i] = cd.makeLabelMap(); }
121                                else             { maps[i] = null; }
122                        }
123                }
124        }
125
126        /**
127         * DBTableModelオブジェクトに、1行分のデータを追加します。
128         * これ自体は、メソッドの共通化による 拡張をしやすくするために用意しました。
129         *
130         * @og.rev 5.2.1.0 (2010/10/01) 新規作成
131         *
132         * @param values 1行分のデータ配列
133         */
134        protected void setTableColumnValues( final String[] values ) {
135                if( useRenderer ) {
136                        int clmSize = values.length;
137                        for( int clmNo=0; clmNo<clmSize; clmNo++ ) {
138                                if( maps[clmNo] != null ) {
139                                        String val = values[clmNo];
140                                        if( val == null ) { val = ""; }
141                                        else {
142                                                String tmp = (String)maps[clmNo].get( val );
143                                                if( tmp != null ) { values[clmNo] = tmp; }
144                                                else {
145                                                        int sp = val.indexOf( ':' );
146                                                        if( sp >= 0 ) {
147                                                                values[clmNo] = val.substring( 0,sp );
148                                                        }
149                                                }
150                                        }
151                                }
152                        }
153                }
154
155                table.addColumnValues( values );
156        }
157
158        /**
159         * 1行のデータを テーブルモデルにセットするように分割します。
160         *
161         * なお、読込みは,NAME項目分を読み込みます。データ件数が少ない場合は、
162         * "" をセットしておきます。
163         * データの分割は、separator文字を使用します。
164         *
165         * @og.rev 3.3.3.1 (2003/07/18) ファイルリード/ライト時に後ろスペースの除去を行います。
166         * @og.rev 3.7.0.5 (2005/04/11) useNumber 属性を考慮します。
167         *
168         * @param       data    1行のデータ
169         * @param       clmSize カラム数
170         *
171         * @return      分割された文字列配列
172         */
173        protected String[] readData( final String data,final int clmSize ) {
174                String[] rtnData = new String[ clmSize ];
175                CSVTokenizer token = new CSVTokenizer( data,separator.charAt(0) );
176                // 超イレギュラー処理 最初の separator 以前の文字は無視する。
177                // 3.7.0.5 (2005/04/11)
178                if( useNumber ) { token.nextToken(); }    // 先頭は行番号のため無視する。
179
180                int clmNo = 0;
181                while( token.hasMoreTokens() ) {
182                        String val = StringUtil.csvOutQuote( token.nextToken() );
183                        if( val != null && val.startsWith( "'0" ) ) {
184                                rtnData[clmNo++] = StringUtil.rTrim( val.substring( 1 ) );
185                        }
186                        else {
187                                rtnData[clmNo++] = StringUtil.rTrim( val );
188                        }
189                        if( clmNo >= clmSize ) { break; }       // 3.7.0.5 (2005/04/11) 多い場合は、以降を無視する。
190                }
191                // EXCEL が、終端TABを削除してしまうため、少ない場合は埋める。
192                for( int i=clmNo; i<clmSize; i++ ) {
193                        rtnData[i] = "";
194                }
195
196                return rtnData;
197        }
198
199        /**
200         * 内部の DBTableModel を返します。
201         *
202         * @return      テーブルモデル
203         */
204        public DBTableModel getDBTableModel() {
205                return table;
206        }
207
208        /**
209         * データを読み込む場合の,区切り文字をセットします。
210         *
211         * なお,このメソッドは,サブクラスによっては,使用しない場合があります。
212         * もし,使用しないサブクラスを作成する場合は, UnsupportedOperationException
213         * を throw するように,サブクラスで実装して下さい。
214         *
215         * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。
216         *
217         * @param   sep 区切り文字
218         */
219        public void setSeparator( final String sep ) {
220                if( sep != null ) { this.separator = sep; }
221        }
222
223        /**
224         * データを書き込む場合の,区切り文字を返します。
225         *
226         * @return      区切り文字
227         */
228        public String getSeparator() {
229                return separator;
230        }
231
232        /**
233         * DBTableModelのデータとして登録する最大件数をこの値に設定します
234         *              (初期値:DB_MAX_ROW_COUNT[={@og.value org.opengion.hayabusa.common.SystemData#DB_MAX_ROW_COUNT}])。
235         * サーバーのメモリ資源と応答時間の確保の為です。
236         *
237         * @return  最大検索件数
238         */
239        public int getMaxRowCount() {
240                return maxRowCount;
241        }
242
243        /**
244         * DBTableModelのデータとして登録する最大件数をこの値に設定します
245         *              (初期値:DB_MAX_ROW_COUNT[={@og.value org.opengion.hayabusa.common.SystemData#DB_MAX_ROW_COUNT}])。
246         * サーバーのメモリ資源と応答時間の確保の為です。
247         *
248         * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。
249         * @og.rev 5.5.8.5 (2012/11/27) 0を無制限として処理します。
250         *
251         * @param   maxRowCount 最大検索件数
252         */
253        public void setMaxRowCount( final int maxRowCount ) {
254                this.maxRowCount = ( maxRowCount > 0 ) ? maxRowCount : Integer.MAX_VALUE ;
255        }
256
257        /**
258         * DBTableModelのデータとしてEXCELファイルを読み込むときのシート名を設定します。
259         * これにより、複数の形式の異なるデータを順次読み込むことや、シートを指定して
260         * 読み取ることが可能になります。
261         * sheetNos と sheetName が同時に指定された場合は、sheetNos が優先されます。エラーにはならないのでご注意ください。
262         * のでご注意ください。
263         * このメソッドは、isExcel() == true の場合のみ利用されます。
264         *
265         * ※ このクラスでは実装されていません。
266         *
267         * @og.rev 3.5.4.2 (2003/12/15) 新規追加
268         *
269         * @param   sheetName シート名
270         */
271        public void setSheetName( final String sheetName ) {
272                String errMsg = "このメソッドは、EXCEL追加機能ですので、使用できません。";
273                throw new UnsupportedOperationException( errMsg );
274        }
275
276        /**
277         * EXCELファイルを読み込むときのシート番号を指定します(初期値:0)。
278         *
279         * EXCEL読み込み時に複数シートをマージして取り込みます。
280         * シート番号は、0 から始まる数字で表します。
281         * ヘッダーは、最初のシートのカラム位置に合わせます。(ヘッダータイトルの自動認識はありません。)
282         * よって、指定するシートは、すべて同一レイアウトでないと取り込み時にカラムのずれが発生します。
283         * 
284         * シート番号の指定は、カンマ区切りで、複数指定できます。また、N-M の様にハイフンで繋げることで、
285         * N 番から、M 番のシート範囲を一括指定可能です。また、"*" による、全シート指定が可能です。
286         * これらの組み合わせも可能です。( 0,1,3,5-8,10-* )
287         * ただし、"*" に関しては例外的に、一文字だけで、すべてのシートを表すか、N-* を最後に指定するかの
288         * どちらかです。途中には、"*" は、現れません。
289         * シート番号は、重複(1,1,2,2)、逆転(3,2,1) での指定が可能です。これは、その指定順で、読み込まれます。
290         * sheetNos と sheetName が同時に指定された場合は、sheetNos が優先されます。エラーにはならないのでご注意ください。
291         * このメソッドは、isExcel() == true の場合のみ利用されます。
292         * 
293         * 初期値は、0(第一シート) です。
294         *
295         * ※ このクラスでは実装されていません。
296         *
297         * @og.rev 5.5.7.2 (2012/10/09) 新規追加
298         *
299         * @param   sheetNos EXCELファイルのシート番号(0から始まる)
300         * @see         #setSheetName( String ) 
301         */
302        public void setSheetNos( final String sheetNos ) {
303                String errMsg = "このメソッドは、EXCEL追加機能ですので、使用できません。";
304                throw new UnsupportedOperationException( errMsg );
305        }
306
307        /**
308         * EXCELファイルを読み込むときのシート単位の固定値を設定するためのカラム名とアドレスを指定します。
309         * カラム名は、カンマ区切りで指定します。
310         * 対応するアドレスを、EXCEL上の行-列を0から始まる整数でカンマ区切りで指定します。
311         * これにより、シートの一か所に書かれている情報を、DBTableModel のカラムに固定値として
312         * 設定することができます。
313         * 例として、DB定義書で、テーブル名をシートの全レコードに設定したい場合などに使います。
314         * このメソッドは、isExcel() == true の場合のみ利用されます。
315         *
316         * @og.rev 5.5.8.2 (2012/11/09) 新規追加
317         *
318         * @param   constKeys 固定値となるカラム名(CSV形式)
319         * @param   constAdrs 固定値となるアドレス(行-列,行-列,・・・)
320         */
321        public void setSheetConstData( final String constKeys,final String constAdrs ) {
322                String errMsg = "このメソッドは、EXCEL追加機能ですので、使用できません。";
323                throw new UnsupportedOperationException( errMsg );
324        }
325
326        /**
327         * ここに指定されたカラム列に NULL が現れた時点で読み取りを中止します。
328         *
329         * これは、指定のカラムは必須という事を条件に、そのレコードだけを読み取る処理を行います。
330         * 複数Sheetの場合は、次のSheetを読みます。
331         * 現時点では、Excel の場合のみ有効です。
332         *
333         * @og.rev 5.5.8.2 (2012/11/09) 新規追加
334         *
335         * @param   clm カラム列
336         */
337        public void setNullBreakClm( final String clm ) {
338                String errMsg = "このメソッドは、EXCEL追加機能ですので、使用できません。";
339                throw new UnsupportedOperationException( errMsg );
340        }
341
342        /**
343         * このクラスが、EXCEL対応機能を持っているかどうかを返します。
344         *
345         * EXCEL対応機能とは、シート名のセット、読み込み元ファイルの
346         * Fileオブジェクト取得などの、特殊機能です。
347         * 本来は、インターフェースを分けるべきと考えますが、taglib クラス等の
348         * 関係があり、問い合わせによる条件分岐で対応します。
349         *
350         * @og.rev 3.5.4.3 (2004/01/05) 新規追加
351         *
352         * @return      EXCEL対応機能を持っているかどうか
353         */
354        public boolean isExcel() {
355                return false;
356        }
357
358        /**
359         * 読み取り元ファイル名をセットします。(DIR + Filename)
360         * これは、EXCEL追加機能として実装されています。
361         * ※ このクラスでは実装されていません。
362         *
363         * @og.rev 3.5.4.3 (2004/01/05) 新規作成
364         *
365         * @param   filename 読み取り元ファイル名
366         */
367        public void setFilename( final String filename ) {
368                String errMsg = "このメソッドは、EXCEL追加機能ですので、使用できません。";
369                throw new UnsupportedOperationException( errMsg );
370        }
371
372        /**
373         * 読み取り元ファイルのカラム列を、外部(タグ)より指定します。
374         * ファイルに記述された #NAME より優先して使用されます。
375         *
376         * @og.rev 3.5.4.5 (2004/01/23) 新規作成
377         *
378         * @param   clms 読み取り元ファイルのカラム列(カンマ区切り文字)
379         */
380        public void setColumns( final String clms ) {
381                columns = clms ;
382        }
383
384        /**
385         * 読み取り元ファイルのエンコード文字列を指定します。
386         * ファイルは、BufferedReader で受け取る為、本来は、エンコードは不要ですが、
387         * 固定長ファイルの読み取り時のバイトコード分割時に、指定のエンコードで
388         * 分割する必要があります。(例えば、半角文字は、Shift_JIS では、1バイト)
389         *
390         * @og.rev 3.5.4.5 (2004/01/23) 新規作成
391         *
392         * @param   enc ファイルのエンコード文字列
393         */
394        public void setEncode( final String enc ) {
395                encode = enc;
396        }
397
398        /**
399         * 読み取り元ファイルのエンコード文字列を取得します。
400         * ファイルは、BufferedReader で受け取る為、本来は、エンコードは不要ですが、
401         * 固定長ファイルの読み取り時のバイトコード分割時に、指定のエンコードで
402         * 分割する必要があります。(例えば、半角文字は、Shift_JIS では、1バイト)
403         *
404         * @og.rev 3.5.4.5 (2004/01/23) 新規作成
405         *
406         * @return      ファイルのエンコード文字列
407         */
408        protected String getEncode() {
409                return encode;
410        }
411
412        /**
413         * 行番号情報を指定[true:使用している/false:していない]します(初期値:true)。
414         *
415         * 通常のフォーマットでは、各行の先頭に行番号が出力されています。
416         * 読み取り時に、#NAME 属性を使用する場合は、この行番号を無視しています。
417         * #NAME 属性を使用せず、columns 属性でカラム名を指定する場合(他システムの
418         * 出力ファイルを読み取るケース等)では、行番号も存在しないケースがあり、
419         * その様な場合に、useNumber="false" を指定すれば、データの最初から読み取り始めます。
420         * この場合、出力データのカラムの並び順が変更された場合、columns 属性も
421         * 指定しなおす必要がありますので、できるだけ、#NAME 属性を使用するように
422         * してください。
423         * なお、EXCEL 入力には、この設定は適用されません。(暫定対応)
424         * 初期値は、true(使用する) です。
425         *
426         * @og.rev 3.7.0.5 (2005/04/11) 新規追加
427         *
428         * @param       useNumber       行番号情報  [true:使用する/false:使用しない]
429         */
430        public void setUseNumber( final boolean useNumber ) {
431                this.useNumber = useNumber ;
432        }
433
434        /**
435         * データの読み始めの初期値を取得します。
436         *
437         * TAB区切りテキストやEXCEL等のデータの読み始めの初期値を指定します。
438         * ファイルの先頭行が、0行としてカウントしますので、設定値は、読み飛ばす
439         * 件数になります。(1と指定すると、1件読み飛ばし、2行目から読み込みます。)
440         * 読み飛ばしは、コメント行などは、無視しますので、実際の行数分読み飛ばします。
441         * #NAME属性や、columns 属性は、有効です。
442         *
443         * @og.rev 5.1.6.0 (2010/05/01) 新規作成
444         *
445         * @return      読み始めの初期値
446         */
447        public int getSkipRowCount() {
448                return skipRowCount ;
449        }
450
451        /**
452         * データの読み飛ばし件数を設定します。
453         *
454         * TAB区切りテキストやEXCEL等のデータの読み始めの初期値を指定します。
455         * ファイルの先頭行が、0行としてカウントしますので、設定値は、読み飛ばす
456         * 件数になります。(1と指定すると、1件読み飛ばし、2行目から読み込みます。)
457         * 読み飛ばしは、コメント行などは、無視しますので、実際の行数分読み飛ばします。
458         * #NAME属性や、columns 属性は、有効です。
459         *
460         * @og.rev 5.1.6.0 (2010/05/01) 新規作成
461         *
462         * @param       count 読み始めの初期値
463         */
464        public void setSkipRowCount( final int count ) {
465                skipRowCount = count;
466        }
467
468        /**
469         * 読取処理でラベルをコードリソースに逆変換を行うかどうかを指定します。
470         *
471         * TableWriter_Renderer 系のクラスで出力した場合は、コードリソースがラベルで出力されます。
472         * そのファイルを読み取ると、当然、エラーになります。
473         * ここでは、コードリソースのカラムに対して、ラベルからコードを求める逆変換を行うことで、
474         * Renderer 系で出力したファイルを取り込むことができるようにします。
475         *
476         * ここでは、TableWriter 系と同様に、TableReader_Renderer 系のクラスを作るのではなく、
477         * 属性値のフラグで、制御します。
478         * 将来的には、TableWriter 系も廃止して、同様のフラグで制御するように変更する予定です。
479         *
480         * @og.rev 5.2.1.0 (2010/10/01) 新規作成
481         *
482         * @param       useRenderer     コードリソースのラベル変換を行うかどうかを指定
483         */
484        public void setUseRenderer( final boolean useRenderer ) {
485                this.useRenderer = useRenderer;
486        }
487
488        /**
489         * 行番号情報を、使用している(true)/していない(false)を返します。
490         *
491         * 通常のフォーマットでは、各行の先頭に行番号が出力されています。
492         * 読み取り時に、#NAME 属性を使用する場合は、この行番号を無視しています。
493         * #NAME 属性を使用せず、columns 属性でカラム名を指定する場合(他システムの
494         * 出力ファイルを読み取るケース等)では、行番号も存在しないケースがあり、
495         * その様な場合に、useNumber="false" を指定すれば、データの最初から読み取り始めます。
496         * この場合、出力データのカラムの並び順が変更された場合、columns 属性も
497         * 指定しなおす必要がありますので、できるだけ、#NAME 属性を使用するように
498         * してください。
499         * なお、EXCEL 入力には、この設定は適用されません。(暫定対応)
500         * 初期値は、true(使用する) です。
501         *
502         * @og.rev 3.7.0.5 (2005/04/11) 新規追加
503         * @og.rev 4.0.0.0 (2007/07/20) メソッド名変更(getUseNumber() ⇒  isUseNumber())
504         *
505         * @return      行番号情報を、使用している(true)/していない(false)を指定
506         */
507        protected boolean isUseNumber() {
508                return useNumber ;
509        }
510
511        /**
512         * デバッグ情報を出力するかどうかを指定します。
513         *
514         * EXCELなどを読み取る場合、シートマージで読み取ると、エラー時の行番号が、連番になるため、
515         * どのシートなのか、判らなくなります。
516         * そこで、どうしてもわからなくなった場合に備えて、デバッグ情報を出力できるようにします。
517         * 通常は使用しませんので、設定を無視します。
518         * 初期値は、false:デバッグ情報を出力しない です。
519         *
520         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
521         *
522         * @param       useDebug        デバッグ情報を出力するかどうかを指定
523         */
524        public void setDebug( final boolean useDebug ) {
525                this.useDebug = useDebug;
526        }
527
528        /**
529         * デバッグ情報を出力するかどうかを取得します。
530         *
531         * EXCELなどを読み取る場合、シートマージで読み取ると、エラー時の行番号が、連番になるため、
532         * どのシートなのか、判らなくなります。
533         * そこで、どうしてもわからなくなった場合に備えて、デバッグ情報を出力できるようにします。
534         *
535         * @og.rev 5.5.7.2 (2012/10/09) 新規作成
536         *
537         * @return      デバッグ情報を出力するかどうか(true:する/false:しない)
538         */
539        protected boolean isDebug() {
540                return useDebug ;
541        }
542}