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