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.fukurou.model;
017
018import org.opengion.fukurou.util.StringUtil;    // 6.2.0.0 (2015/02/27)
019import org.opengion.fukurou.util.FileInfo;              // 6.2.0.0 (2015/02/27)
020
021import java.util.List;
022import java.util.ArrayList;
023import java.util.Map;                                                   // 6.1.0.0 (2014/12/26) ConstData を Mapで管理
024import java.util.HashMap;                                               // 6.1.0.0 (2014/12/26) ConstData を Mapで管理
025import java.util.Arrays;                                                // 6.2.0.0 (2015/02/27)
026import java.io.File;                                                    // 6.2.0.0 (2015/02/27)
027
028/**
029 * EXCELやテキストファイルを、イベント方式に準拠して、読み込み処理を行います。
030 * TableModelHelperイベントは、openGion形式のファイル読み取りに準拠した方法をサポートします。
031 * ※ openGion形式のEXCEL/テキストファイルとは、#NAME 列に、カラム名があり、#で始まる
032 *    レコードは、コメントとして判断し、読み飛ばす処理の事です。
033 *
034 * このイベントクラスは、サブクラスを作成し、EXCEL関連の EventReader_XLS、EventReader_XLSX
035 * クラスや、EventReader_TEXT などのテキスト関連のクラスで、eventReader メソッドの引数に指定します。
036 * EventReader_XLS と、EventReader_XLSX は、対象のEXCEL形式が異なりますが、実際は、
037 * POIUtil#eventReader( String , TableModelHelper ) を使用すれば、拡張子に応じて使用するクラスを
038 * 選択します。
039 *
040 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
041 * @og.rev 6.2.0.0 (2015/02/27) パッケージ変更(util → model),クラス名変更(ExcelReaderEvent → TableModelHelper)
042 * @og.group ファイル入力
043 *
044 * @version  6.0
045 * @author   Kazuhiko Hasegawa
046 * @since    JDK7.0,
047 */
048public class TableModelHelper {
049        private int             nowRowNo        = -1;           // 現在の行番号
050        private boolean isColSkip       ;                       // カラムスキップ中かどうか(true:スキップ中)
051        private boolean isNowName       ;                       // カラム名配列の収集中かどうか(true:収集中)
052        private boolean isReadBreak     ;                       // 読み取り処理を途中で中止するかどうか(true:中止)
053        private String  nullBreakClm;                   // データがnullまたはゼロ文字列の場合に、Sheet読み取りを停止
054        private String  nullSkipClm     ;                       // 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加
055
056        private int             skipRowCnt      ;                       // 読み飛ばす行数(読み飛ばし中でも ContDataは取得する)
057        private int             nBrkClmNo = -1;                 // nullBreakClmの列番号
058        private int             nSkpClmNo = -1;                 // 6.2.3.0 (2015/05/01) nullSkipClmの列番号
059
060        private int              clmSize        = -1 ;          // カラムサイズ(-1は未設定)
061        private String[] names          ;                       // カラム名配列
062        private String[] vals           ;                       // 値の文字列配列(1行分)
063        private boolean useVals         ;                       // 値配列に設定されたか?
064
065        private List<Integer> colList     ;               // カラム番号:name が null かゼロ文字列の場合は、飛ばします。
066        private List<String>  nmsList     ;               // カラム番号に対応したカラム名配列
067
068        private ConstData       cnstData        ;
069
070        private boolean         useDebug        ;               // 6.2.0.0 (2015/02/27) デバッグ情報の出力するかどうか。新規追加
071
072        /**
073         * ファイルの読み取り開始時にイベントが発生します。
074         *
075         * 新しいファイルの読み取り開始毎に、1回呼ばれます。
076         * 戻り値が、true の場合は、そのファイルの読み取りを継続します。
077         * false の場合は、そのファイルの読み取りは行われません。
078         * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、
079         * super.startFile(String,int) してください。
080         * 初期実装では、true を返します。
081         *
082         * @og.rev 6.2.0.0 (2015/02/27) 新規作成
083         *
084         * @param   file  読み取りファイル
085         * @return  読取継続するか [true:継続/false:読取らない]
086         */
087        public boolean startFile( final File file ) {
088                if( useDebug ) { System.out.println( "startFile=[" + file + "]" ); }
089                // ファイル属性を設定する。
090                if( cnstData != null ) { cnstData.putConstFile( file ); }
091                return true;
092        }
093
094        /**
095         * ファイルの読み取り終了時にイベントが発生します。
096         *
097         * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、
098         * super.endFile(File) してください。
099         *
100         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
101         * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加
102         *
103         * @param   file  読み取りファイル
104         */
105        public void endFile( final File file ) {
106                if( useDebug ) { System.out.println( "endFile=[" + file + "]" ); }
107                isReadBreak = false;                    // 読み取り再開
108                endRow();                                               // 行終了処理
109                if( cnstData != null ) { cnstData.clearValsMap( ConstData.END_FILE ); }
110        }
111
112        /**
113         * シートの読み取り開始時にイベントが発生します。
114         *
115         * ※ EXCEL関係以外の読み取りでは、このイベントは発生しません。
116         *
117         * 新しいシートの読み取り開始毎に、1回呼ばれます。
118         * 戻り値が、true の場合は、そのシートの読み取りを継続します。
119         * false の場合は、そのシートの読み取りは行わず、次のシートまで
120         * イベントは発行されません。
121         * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、
122         * super.startSheet(String,int) してください。
123         * 初期実装では、true を返します。
124         *
125         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
126         *
127         * @param   shtNm  シート名
128         * @param   shtNo  シート番号(0〜)
129         * @return  読取継続するか [true:継続/false:読取らない]
130         */
131        public boolean startSheet( final String shtNm,final int shtNo ) {
132                if( useDebug ) { System.out.println( "startSheet=[" + shtNm + "], No=[" + shtNo + "]" ); }
133                // シート名を設定する。
134                if( cnstData != null ) { cnstData.putConstSheet( shtNm ); }
135                return true;
136        }
137
138        /**
139         * シートの読み取り終了時にイベントが発生します。
140         *
141         * ※ EXCEL関係以外の読み取りでは、このイベントは発生しません。
142         *
143         * #columnNames( String[] ) や、#values( String[] ,int ) などは、行の処理が完了した時点で
144         * イベントが呼ばれるため、一番最後のレコードの終了条件が判りません。
145         * そこで、このイベントを呼ぶことで、シートの終了(=最終行の終了)処理を行うことができます。
146         * 
147         * 引数のシート番号は、参考情報で、#startSheet( String,int ) で呼ばれたシート番号と
148         * 比較できるようにしています。
149         * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、
150         * super.endSheet(int) してください。
151         *
152         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
153         * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加
154         *
155         * @param   shtNo  シート番号(0〜)
156         */
157        public void endSheet( final int shtNo ) {
158                if( useDebug ) { System.out.println( "endSheet No=[" + shtNo + "]" ); }
159                isReadBreak = false;                    // 読み取り再開
160                endRow();                                               // 行終了処理
161                if( cnstData != null ) { cnstData.clearValsMap( ConstData.END_SHEET ); }
162        }
163
164        /**
165         * 読み取り状態の時に、rowNo にある行データを引数にイベントが発生します。
166         *
167         * ※ 主に、行データの読み込み処理で使用されるメソッドです。
168         *    このメソッドは、EventReader#eventReader( String , TableModelHelper )メソッドの
169         *    処理の過程で、設定されます。
170         *    このメソッドから、各種イベントが発行されます。
171         *
172         * 行データをセパレータで分解したのち、value( String ,int ,int ) メソッドを呼びます。
173         * ただし、行データが、null、空文字列、#で始まる場合は、呼ばれません。
174         *
175         * @og.rev 6.2.0.0 (2015/02/27) 新規作成
176         * @og.rev 6.2.1.0 (2015/03/13) 先頭に、'0 が含まれる場合のセミコロンや、前後のダブルクオートは削除
177         * @og.rev 6.2.5.0 (2015/06/05) デバック時に1行単位に出力するのを止めます。
178         *
179         * @param   line        行データ
180         * @param   rowNo       行番号(0〜)
181         * @param   sepa        セパレータ
182         */
183        protected void value( final String line,final int rowNo,final char sepa ) {
184                nowRowNo = rowNo;       // 現在行の設定
185                // 値が存在する場合のみ、処理する。
186                if( line!=null && !line.isEmpty()  ) {
187                        // 6.2.5.0 (2015/06/05) デバック時に1行単位に出力するのを止めます。
188        //              if( useDebug ) { System.out.println( "rowNo[" + rowNo + "], line=[" + line + "]" ); }
189
190                        // 先頭カラムのコメント判断と、#NAME列判断
191                        if( line.charAt(0)=='#' ) {
192                                // 互換性の問題。#NAME は、大文字小文字両方対応できないといけない。( 5 == "#NAME".length() の事)
193                                if( clmSize <= 0 && line.length() >= 5 && "#NAME".equalsIgnoreCase( line.substring( 0,5 ) ) ) {
194
195                                        colList = new ArrayList() ;     // 初期化
196                                        nmsList = new ArrayList() ;             // 初期化
197                                        final String[] tmpNm = StringUtil.csv2Array( line ,sepa );
198                                        for( int colNo=1; colNo<tmpNm.length; colNo++ ) {    // #NAME を無視(colNo=1〜)
199                                                final String nm = tmpNm[colNo];
200                                                // #NAME 設定も、null や、ゼロ文字列の場合は、登録しない。(一番上の if で対処)
201                                                if( nm != null && !nm.isEmpty() ) {
202                                                        colList.add( Integer.valueOf( colNo ) );
203                                                        nmsList.add( nm );
204                                                }
205                                        }
206                                        isNowName = true;
207                                }
208                        }
209                        // clmSize > 0 は、#NAME が設定済みという意味
210                        else if( clmSize > 0 && skipRowCnt <= rowNo ) {           // skipRowCnt は、値セットだけ影響する。
211                                final String[] tmpVals = StringUtil.csv2Array( line ,sepa );
212        //                      if( cnstData != null ) { tmpVals = cnstData.getConstVals( tmpVals ); }
213        //                      int min = Math.min( clmSize,tmpVals.length );
214
215                                for( int i=0; i<clmSize; i++ ) {
216                                        final int indx = colList.get( i );                      // カラム番号の位置が 値配列の配列番号
217                                        if( indx >= 0 && indx < tmpVals.length ) {
218                                                vals[i] = StringUtil.csvOutQuote( tmpVals[indx] );              // 6.2.1.0 (2015/03/13)
219                                        }
220                                }
221                                useVals = true;
222        //                      values( vals , rowNo );                                                 // イベント発行(現在行)
223                        }
224
225                        endRow();                               // 行末処理実行。名前設定処理が走ります。
226                }
227        }
228
229        /**
230         * 読み取り状態の時に、rowNo,colNo にあるセルの値を引数にイベントが発生します。
231         *
232         * ※ 主に、XCEL関係の読み取り処理で使用されるメソッドです。
233         *    このメソッドは、EventReader#eventReader( String , TableModelHelper )メソッドの
234         *    処理の過程で、設定されます。
235         *    このメソッドから、各種イベントが発行されます。
236         *
237         * 戻り値が、true の場合は、その行の読み取りを継続します。
238         * false の場合は、その行の読み取りは行わず、次の行まで
239         * イベントは発行されません。
240         *
241         * openGion での標準的な処理は、colNo==0 の時に、val の先頭が、# で
242         * 始まる場合は、その行はスキップします。
243         * ここでの return は、#isSkip( int ) と逆になりますので、ご注意ください。
244         * 初期実装では、#NAME処理、行スキップ、行終了処理等を実行します。
245         * オーバーライドする場合は、super.value(String,int,int) してください。
246         *
247         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
248         * @og.rev 6.2.2.0 (2015/03/27) 先頭に、'0 が含まれる場合のセミコロンや、前後のダブルクオートは削除
249         * @og.rev 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加
250         *
251         * @param   val     文字列値
252         * @param   rowNo   行番号(0〜)
253         * @param   colNo   列番号(0〜)
254         * @return  読み取りするかどうか(true:読み取りする/false:読み取りしない)
255         * @see         #isSkip( int )
256         */
257        protected boolean value( final String val,final int rowNo,final int colNo ) {
258        //      if( useDebug ) { System.out.println( "R[" + rowNo + "," + colNo + "]=" + val ); }
259                // 値が存在する場合のみ、処理する。
260                if( val!=null && val.length()>0 ) {
261                        // 行番号が異なった場合は、行の終了処理を実行する。
262                        if( nowRowNo != rowNo ) {
263                                endRow();                                                                       // 行終了処理
264                                nowRowNo = rowNo;                                                       // 現在行の設定
265                        }
266                        // 固定文字の設定なので、コメントもスキップも無関係
267                        if( cnstData != null ) { cnstData.putConstValue( val,rowNo,colNo ); }
268
269                        // 先頭カラムのコメント判断と、#NAME列判断
270                        if( colNo==0 && val.charAt(0)=='#' ) {
271                                if( "#NAME".equalsIgnoreCase( val ) && clmSize <= 0 ) {      // clmSize <= 0 は、#NAME未設定という意味
272                                        isNowName = true ;
273                                        colList = new ArrayList() ;     // 初期化
274                                        nmsList = new ArrayList() ;             // 初期化
275                                }
276                                isColSkip = !isNowName;                                         // #NAME の場合は、false(SKIPしない)
277                        }
278                        else if( isNowName ) {                                                  // #NAME処理中
279                                // #NAME 設定も、null や、ゼロ文字列の場合は、登録しない。(一番上の if で対処)
280                                colList.add( Integer.valueOf( colNo ) );
281                                nmsList.add( val );
282                        }
283                        // 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加
284                        else if( nSkpClmNo >= 0 && StringUtil.isNull( vals[nSkpClmNo] ) ) {          // nullSkipClm 処理
285                                isColSkip = true;       // Skip する
286                        }
287                        // clmSize > 0 は、#NAME が設定済みという意味
288                        else if( clmSize > 0 && skipRowCnt <= rowNo ) {                                           // skipRowCnt は、値セットだけ影響する。
289                                final int indx = colList.indexOf( Integer.valueOf( colNo ) );   // カラム番号の位置が 値配列の配列番号
290                                if( indx >= 0 ) {
291                                        vals[indx] = StringUtil.csvOutQuote( val );                                     // 6.2.2.0 (2015/03/27)
292                                        useVals = true;
293                                }
294                        }
295                }
296
297                return !isColSkip;
298        }
299
300        /**
301         * rowNo を元に、この行をスキップするかどうか判定のイベントが発生します。
302         *
303         * ※ EXCEL関係の列毎にイベントが発生する場合の列処理をスキップするのが目的です。
304         *    行単位に読み込む場合や、行のループに入れても、意味はありません。
305         *    引数から、行のループに入れてしまいそうですが、行列のループに入れてください。
306         *
307         * ※ 主に、EXCEL関係の読み取り処理で使用されるメソッドです。
308         *    このメソッドは、EventReader#eventReader( String , TableModelHelper )メソッドの
309         *    処理の過程で、設定されます。
310         *    このメソッドから、各種イベントが発行されます。
311         *
312         * 戻り値が、true の場合は、その行の読み取りをスキップします。
313         * false の場合は、読み取り処理を継続します。
314         * スキップ中は、行に関するイベントは発行されません。
315         *
316         * 値(val)を求める前に、行情報を入手し、スキップ中の現在行と同じ場合に、スキップ(=true)と判定します。
317         * スキップ中かどうかは、#value( String,int,int ) で、判定します。
318         * 初期実装では、スキップ中 かつ 現在行と同じ行の場合、または、シートブレーク中の場合に、true を返します。
319         *
320         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
321         * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加
322         *
323         * @param   rowNo   行番号(0〜)
324         * @return  スキップするかどうか(true:スキップする/false:スキップしない)
325         * @see         #value( String,int,int )
326         */
327        protected boolean isSkip( final int rowNo ) {
328                return isColSkip && nowRowNo == rowNo || isReadBreak ;
329        }
330
331        /**
332         * 行の終了時に実行されます。
333         *
334         * ここでは、rowNo がブレークするか、シート終了時に呼ぶことで、
335         * 一番最後の行の処理を行います。
336         *
337         * #NAME 処理中の場合(isNowName == true) は、カラム名配列を作成して、
338         * columnNames イベントを呼び出します。
339         * 値配列が設定されている場合(useVals == true) は、values イベントを
340         * 呼び出します。
341         * #NAME 処理が完了している場合は、値配列の初期化を行います。
342         * そのうえで、スキップの解除(isColSkip = false)と行データ
343         * 未設定(useVals = false)に、初期化します。
344         *
345         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
346         * @og.rev 6.2.2.0 (2015/03/27) Overflow処理は、Tag側に戻す。
347         *
348         * @see         #columnNames( String[] )
349         * @see         #values( String[],int )
350         */
351        private void endRow() {
352                if( isNowName ) {                                                               // #NAME 処理中の場合
353                        clmSize = colList.size();
354                        names   = nmsList.toArray( new String[clmSize] );
355                        if( nullBreakClm != null ) {
356                                for( int i=0; i<clmSize; i++ ) {
357                                        if( nullBreakClm.equalsIgnoreCase( names[i] ) ) {
358                                                nBrkClmNo = i;
359                                                break;
360                                        }
361                                }
362                        }
363                        // 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加
364                        if( nullSkipClm != null ) {
365                                for( int i=0; i<clmSize; i++ ) {
366                                        if( nullSkipClm.equalsIgnoreCase( names[i] ) ) {
367                                                nSkpClmNo = i;
368                                                break;
369                                        }
370                                }
371                        }
372
373                        if( cnstData != null ) { cnstData.setColumns( names ); }
374                        columnNames( names.clone() );                           // イベント
375                        isNowName = false;
376                        nmsList   = null ;              // 不要
377                }
378                else if( useVals ) {                                                    // 値配列が設定されている場合
379                        if( nBrkClmNo >= 0 && StringUtil.isNull( vals[nBrkClmNo] ) ) {               // nullBreakClm 処理
380                                isReadBreak = true;                                                                                             // ReadBreak する。
381                        }
382                        else {
383                                if( cnstData != null ) { vals = cnstData.getConstVals( vals ); }
384                                values( vals , nowRowNo );                                      // イベント発行(現在行)
385                        }
386                }
387                if( clmSize > 0 ) {                                                          // clmSize > 0 は、#NAME が設定済みという意味
388                        vals = new String[clmSize];                                     // 値配列の初期化
389                }
390
391                isColSkip = false;                                              // スキップの解除
392                useVals   = false;                                              // 行データ未設定にする。
393        }
394
395        /**
396         * シート数のイベントが発生します。
397         *
398         * ※ EXCEL関係以外の読み取りでは、このイベントは発生しません。
399         *
400         * 処理の開始前に、シート数のイベントが発生します。
401         * これを元に、処理するシート番号の選別が可能です。
402         *
403         * @og.rev 6.1.0.0 (2014/12/26) シートの数のイベント
404         *
405         * @param   size  シート数
406         */
407        public void sheetSize( final int size ) {
408                if( useDebug ) { System.out.println( "sheetSize=[" + size + "]" ); }
409        }
410
411        /**
412         * カラム名配列がそろった段階で、イベントが発生します。
413         *
414         * openGion での標準的な処理は、colNo==0 の時に、val の先頭が、#NAME
415         * で始まるレコードを、名前配列として認識します。
416         * #value( String,int,int ) で、この #NAME だけは、継続処理されます。
417         * その上で、#NAME レコードが終了した時点で、カラム名配列が完成するので
418         * そこで初めて、このメソッドが呼ばれます。(EXCEL関係の場合)
419         *
420         * 外部から設定する、#setNames( String , boolean ) 時も同様に呼ばれます。
421         *
422         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
423         *
424         * @param   names  カラム名配列
425         * @see         #value( String,int,int )
426         * @see         #setNames( String , boolean )
427         */
428        public void columnNames( final String[] names ) {
429                if( useDebug ) { System.out.println( "columnNames=[" + Arrays.toString(names) + "]" ); }
430        }
431
432        /**
433         * row にあるセルのオブジェクト値がそろった段階で、イベントが発生します。
434         *
435         * 初期実装は、何もありません。
436         *
437         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
438         *
439         * @param   vals    文字列値の1行分の配列
440         * @param   rowNo   行番号(0〜)
441         */
442        public void values( final String[] vals,final int rowNo ) {
443                if( useDebug ) { System.out.println( "values[" + rowNo + "]=[" + Arrays.toString(vals) + "]" ); }
444        }
445
446        /**
447         * 外部からCSV形式のカラム名文字列を設定します。
448         *
449         * ※ イベント処理実行前の初期化処理です。
450         *
451         * カラム名配列を、#NAME で始まるレコードではなく、外部から指定します。
452         * ここで設定した場合、#columnNames( String[] )イベントも発生します。
453         * null か、長さゼロのカラム名は、設定されません。
454         *
455         * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応
456         *
457         * @param   clms  CSV形式のカラム名文字列
458         * @param       useNumber       行番号情報 [true:使用している/false:していない]
459         * @see         #columnNames( String[] )
460         */
461        public void setNames( final String clms , final boolean useNumber ) {
462                if( clms != null && clms.length() > 0 ) {
463                        if( useDebug ) { System.out.println( "setNames=[" + clms + "]" ); }
464
465                        colList = new ArrayList() ;     // 初期化
466                        nmsList = new ArrayList() ;             // 初期化
467
468                        final String[] nms = StringUtil.csv2Array( clms );
469                        final int adrs = useNumber ? 1:0 ;      // useNumber =true の場合は、1件目(No)は読み飛ばす。
470                        for( int i=0; i<nms.length; i++ ) {
471                                // null か、長さゼロのカラム名は、設定されません。
472                                final String nm = nms[i];
473                                if( nm != null && nm.length() > 0 ) {
474                                        colList.add( Integer.valueOf( i+adrs ) );
475                                        nmsList.add( nm );
476                                }
477                        }
478
479                        isNowName = true;               // 名前設定中
480                        useVals   = false;              // データ未設定
481                        endRow();                               // 行末処理実行。名前設定処理が走ります。
482
483                }
484        }
485
486        /**
487         * カラム名配列が、設定されたかどうか、返します。
488         *
489         * カラム名配列は、#NAME で始まるレコードか、#setNames( String,boolean )メソッドを
490         * 使用して、外部から指定します。
491         * カラム名配列が未設定の場合は、データもセットできないので、処理の最後の判定に使います。
492         *
493         * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応
494         *
495         * @return      カラム名配列が、設定されたかどうか(true:設定済み/false:未設定)
496         * @see         #setNames( String,boolean )
497         */
498        public boolean isNameSet() {
499                return clmSize > 0;                  // clmSize > 0 は、#NAME が設定済みという意味
500        }
501
502        /**
503         * 以降のデータを読み飛ばすかどうかを指定します(初期値:false)。
504         *
505         * データを読み込む途中で、それ以降のデータを読み込む必要がなくなった場合に、
506         * true に設定すると、以降のデータを今のシート/ファイルが終了するまで、スキップします。
507         * 例えば、読み込むべき必須カラムで判定する、nullBreakClm 機能を実現できます。
508         * 初期値は、false:読み飛ばさない です。
509         *
510         * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加
511         *
512         * @param   flag  以降のデータを読み飛ばすかどうか [true:読み飛ばす/false:読み飛ばさない]
513         * @see         #isSkip( int )
514         */
515        public void setReadBreak( final boolean flag ) {
516                isReadBreak = flag ;
517        }
518
519        /**
520         * 先頭データの読み飛ばし件数を設定します。
521         *
522         * ※ イベント処理実行前の初期化処理です。
523         *
524         * データの読み始めの行を指定します。
525         * シート/ファイルの先頭行が、0行としてカウントしますので、設定値は、読み飛ばす
526         * 件数になります。(1と指定すると、1件読み飛ばし、2件目から読み込みます。)
527         * 読み飛ばしは、コメント行などは、無視しますので、実際の行数分読み飛ばします。
528         * #NAME属性や、columns 属性は、読み飛ばし中でも処理対象行になります。
529         *
530         * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
531         *
532         * @param       count 読み始めの初期値(0なら、読み飛ばしなし)
533         */
534        public void setSkipRowCount( final int count ) {
535                skipRowCnt = count;
536        }
537
538        /**
539         * ここに指定されたカラム列に NULL/ゼロ文字列 が現れた時点でSheetの読み取りを中止します。
540         *
541         * これは、指定のカラムは必須という事を条件に、そのレコードだけを読み取る処理を行います。
542         * 複数Sheetの場合は、次のSheetを読みます。
543         * Sheetが存在しない場合(通常のテキスト等)では、読み取り処理の終了になります。
544         *
545         * @og.rev 6.2.0.0 (2015/02/27) 新規追加
546         *
547         * @param   clm カラム列
548         */
549        public void setNullBreakClm( final String clm ) {
550                nullBreakClm = clm;
551        }
552
553        /**
554         * ここに指定されたカラム列に NULL が現れたレコードは読み飛ばします。
555         *
556         * 例えば、更新対象カラムで、null の場合は、何もしない、などのケースで使用できます。
557         * 複数カラムの場合は、AND条件やOR条件などが、考えられるため、
558         * カラムを一つにまとめて、指定してください。
559         *
560         * @og.rev 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加
561         *
562         * @param   clm カラム列
563         */
564        public void setNullSkipClm( final String clm ) {
565                nullSkipClm = clm;
566        }
567
568        /**
569         * 固定値となるカラム名(CSV形式)と、固定値となるアドレス(行-列,行-列...) or(A1,B3...)を設定します。
570         *
571         * ※ イベント処理実行前の初期化処理です。
572         *
573         * アドレスは、EXCEL上の行-列か、EXCEL列のA1,B3 などの形式を、CSV形式で指定します。
574         * 行列は、EXCELオブジェクトに準拠するため、0から始まる整数です。
575         * 0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。
576         * これにより、シートの一か所に書かれている情報を、DBTableModel のカラムに固定値として
577         * 設定することができます。
578         * 例として、DB定義書で、テーブル名をシートの全レコードに設定したい場合などに使います。
579         *
580         * 5.7.6.3 (2014/05/23) より、
581         *   @EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。
582         *     なお、A1,A2,B1 の記述は、必ず、英字1文字+数字 にしてください。(A〜Zまで)
583         *     EXCEL2007形式で列数が拡張されていますが、列数は制限しています。
584         *   A処理中のEXCELシート名をカラムに割り当てるために、"SHEET" という記号に対応します。
585         * 6.2.0.0 (2015/02/27) より、
586         *   BEXCEL以外では、"FILE","NAME","SUFIX" が使えます。(EXCEL時にも使えます。)
587         *     NAME は、ファイル名から拡張子を取り除いた文字列になります。(AAA/BBB/CCC.xls → CCC)
588         * 例えば、sheetConstKeys="CLM,LANG,NAME" とし、sheetConstAdrs="0-0,A2,SHEET" とすると、
589         * NAMEカラムには、シート名を読み込むことができます。
590         * これは、内部処理の簡素化のためです。
591         *
592         * ちなみに、EXCELのセルに、シート名を表示させる場合の関数は、下記の様になります。
593         * =RIGHT(CELL("filename",$A$1),LEN(CELL("filename",$A$1))-FIND("]",CELL("filename",$A$1)))
594         *
595         * @og.rev 5.5.8.2 (2012/11/09) 新規追加
596         * @og.rev 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応
597         * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応
598         *
599         * @param       constKeys       固定値となるカラム名(CSV形式)
600         * @param       constAdrs       固定値となるアドレス(行-列,行-列...) or(A1,B3...)
601         */
602        public void setConstData( final String constKeys,final String constAdrs ) {
603                if( constKeys != null && !constKeys.isEmpty() && constAdrs != null && !constAdrs.isEmpty() ) {
604                        cnstData = new ConstData( constKeys,constAdrs );
605                        // setNames( String , boolean ) と、このメソッドの呼び出し順に影響がないようにするため。
606                        if( names != null ) { cnstData.setColumns( names ); }
607                }
608        }
609
610        /**
611         * デバッグ情報を出力するかどうか[true:する/false:しない]を指定します。
612         *
613         * EXCELなどを読み取る場合、シートマージで読み取ると、エラー時の行番号が、連番になるため、
614         * どのシートなのか、判らなくなります。
615         * そこで、どうしてもわからなくなった場合に備えて、デバッグ情報を出力できるようにします。
616         * 通常は使用しませんので、設定を無視します。
617         * 初期値は、false:デバッグ情報を出力しない です。
618         *
619         * @og.rev 6.2.0.0 (2015/02/27) デバッグ情報の出力するかどうか。新規追加
620         *
621         * @param       useDebug        デバッグ出力するか [true:する/false:しない]
622         */
623        public void setDebug( final boolean useDebug ) {
624                this.useDebug = useDebug;
625        }
626
627        /**
628         * デバッグ情報を出力するかどうか[true:する/false:しない]を取得します。
629         *
630         * EXCELなどを読み取る場合、シートマージで読み取ると、エラー時の行番号が、連番になるため、
631         * どのシートなのか、判らなくなります。
632         * そこで、どうしてもわからなくなった場合に備えて、デバッグ情報を出力できるようにします。
633         *
634         * @og.rev 6.2.0.0 (2015/02/27) デバッグ情報の出力するかどうか。新規追加
635         *
636         * @return      デバッグ出力 [true:する/false:しない]
637         */
638        protected boolean isDebug() {
639                return useDebug ;
640        }
641
642        /**
643         * EXCELファイルの所定の位置から、固定値を取り出す処理を管理します。
644         * 
645         * この固定値の取出しは、内部処理に、非常に依存しているため、今は、
646         * TableModelHelper クラスに含めていますが、将来的には、分ける予定です。
647         *
648         * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
649         * @og.rev 6.2.0.0 (2015/02/27) 特殊記号(FILE,NAME,SUFIX)追加
650         *
651         * @version  6.0
652         * @author   Kazuhiko Hasegawa
653         * @since    JDK7.0,
654         */
655        private static final class ConstData {
656                /** 内部情報のクリア方法の指定 {@value} */
657                public static final int END_FILE  = 1 ;
658                /** 内部情報のクリア方法の指定 {@value} */
659                public static final int END_SHEET = 2 ;
660
661                // 6.2.0.0 (2015/02/27) 特殊記号処理 追加 (SHEET,FILE,NAME,SUFIX)
662                private static final String KEYS = ",SHEET,FILE,NAME,SUFIX,";
663
664                // @cnstMap は、cnstKey をキーに、拾ってきた値を持っています。(Sheet毎のトランザクション)
665                private final Map<String,String> cnstMap   = new HashMap();                       // 6.1.0.0 (2014/12/26) Mapで管理
666                // ArowcolMap は、rowcol をキーに、アドレスを持っています。
667                private final Map<String,Integer> rowcolMap = new HashMap();              // 6.1.0.0 (2014/12/26) Mapで管理
668                // BvalsMap は、アドレス をキーに、拾ってきた値を持っています。(Sheet毎のトランザクション)
669                private final Map<Integer,String> valsMap   = new HashMap();              // 6.1.0.0 (2014/12/26) Mapで管理
670
671                private int maxRow = -1 ;               // 最大値持っておき、判定処理を早める。
672
673                // ※ rowcolMap を使用する為、必ず #setColumns( String... ) の実行後に行います。
674                private boolean isNameSet ;
675                private File    tmpFile   ;
676                private String  tmpShtNm  ;
677
678                /**
679                 * 固定値となるカラム名(CSV形式)と、固定値となるアドレス(行-列,行-列...) or(A1,B3...)を設定します。
680                 *
681                 * アドレスは、EXCEL上の行-列か、EXCEL列のA1,B3 などの形式を、CSV形式で指定します。
682                 * 行列は、EXCELオブジェクトに準拠するため、0から始まる整数です。
683                 * 0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。
684                 * これにより、シートの一か所に書かれている情報を、DBTableModel のカラムに固定値として
685                 * 設定することができます。
686                 * 例として、DB定義書で、テーブル名をシートの全レコードに設定したい場合などに使います。
687                 *
688                 * 5.7.6.3 (2014/05/23) より、
689                 *   @EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。
690                 *     なお、A1,A2,B1 の記述は、必ず、英字1文字+数字 にしてください。(A〜Zまで)
691                 *     EXCEL2007形式で列数が拡張されていますが、列数は制限しています。
692                 *   A処理中のEXCELシート名をカラムに割り当てるために、"SHEET" という記号に対応します。
693                 * 6.2.0.0 (2015/02/27) より、
694                 *   BEXCEL以外では、"FILE","NAME","SUFIX" が使えます。
695                 *     NAME は、ファイル名から拡張子を取り除いた文字列になります。(AAA/BBB/CCC.xls → CCC)
696                 * 例えば、sheetConstKeys="CLM,LANG,SHT" とし、sheetConstAdrs="0-0,A2,SHEET" とすると、
697                 * SHTカラムには、シート名を読み込むことができます。
698                 * これは、内部処理の簡素化のためです。
699                 *
700                 * ちなみに、EXCELのセルに、シート名を表示させる場合の関数は、下記の様になります。
701                 * =RIGHT(CELL("filename",$A$1),LEN(CELL("filename",$A$1))-FIND("]",CELL("filename",$A$1)))
702                 *
703                 * @og.rev 5.5.8.2 (2012/11/09) 新規追加
704                 * @og.rev 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応
705                 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応
706                 * @og.rev 6.2.0.0 (2015/02/27) 特殊記号(FILE,NAME,SUFIX)の追加対応
707                 *
708                 * @param       constKeys       固定値となるカラム名(CSV形式)
709                 * @param       constAdrs       固定値となるアドレス(行-列,行-列...) or(A1,B3...)
710                 */
711                ConstData( final String constKeys,final String constAdrs ) {
712                        final String[] cnstKeys = constKeys.split( "," );
713                        final String[] row_col  = constAdrs.split( "," );
714
715                        if( cnstKeys.length != row_col.length ) {
716                                final String errMsg = "キーに対するアドレスの個数が不一致です。Keys=[" + constKeys + "]"
717                                                        + " , Adrs=[" + constAdrs + "]" ;
718                                throw new RuntimeException( errMsg );
719                        }
720
721                        // 初期の カラムとアドレス(キーワード)の関連付け
722                        for( int j=0; j<cnstKeys.length; j++ ) {
723                                final String cnstKey = cnstKeys[j].trim();      // 前後の不要なスペースを削除
724                                if( !cnstKey.isEmpty() ) {
725                                        String rowcol = row_col[j].trim();              // 前後の不要なスペースを削除
726                                        if( !rowcol.isEmpty() ) {
727                                                // 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応
728                                                final int sep = rowcol.indexOf( '-' );
729                                                if( sep > 0 ) {
730                                                        final int row = Integer.parseInt( rowcol.substring( 0,sep ) );
731                        //                              int col = Integer.parseInt( rowcol.substring( sep+1 ) );
732                                                        if( maxRow < row ) { maxRow = row; }
733                        //                              rowcol = String.valueOf( (char)('A' + col) ) + String.valueOf( row + 1 ) ;      // row-col 形式なので、不要
734                                                }
735                                                // 6.2.0.0 (2015/02/27) 特殊記号(SHEET,FILE,NAME,SUFIX)の追加対応
736                                                else if( !KEYS.contains( ',' + rowcol + ',' ) ) {
737                                                        final int row = Integer.parseInt( rowcol.substring( 1 ) ) -1;           // C6 の場合、rowは、6-1=5
738                                                        final int col = rowcol.charAt(0) - 'A' ;                                                        // C6 の場合、colは、'C'-'A'=2
739                                                        if( maxRow < row ) { maxRow = row; }
740                                                        rowcol = row + "-" + col ;
741                                                }
742                                                cnstMap.put( cnstKey , rowcol );                // 6.1.0.0 (2014/12/26) cnstMap に行列情報を設定する
743                                        }
744                                }
745                        }
746                }
747
748                /**
749                 * カラム名配列を元に、固定値カラムのアドレスを求めます。
750                 * カラム名配列は、順番に、指定する必要があります。
751                 *
752                 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応
753                 *
754                 * @param       names   カラム列配列(可変長引数)
755                 */
756                void setColumns( final String... names ) {
757                        // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
758                        if( names != null && names.length > 0 ) {
759                                // 5.5.8.2 (2012/11/09) 固定値取得用の cnstIndx の設定を行う。
760                                for( int i=0; i<names.length; i++ ) {
761                                        final String rowcol = cnstMap.get( names[i] );          // cnstKey があれば、rowcol が取得できるはず
762                                        if( rowcol != null ) {
763                                                rowcolMap.put( rowcol , Integer.valueOf( i ) );
764                                        }
765                                }
766                                isNameSet = true;                       // 名前設定がされないと、FILEやSHEET キーワードが使えない。
767
768                                if( tmpFile  != null ) { putConstFile(  tmpFile  ); }
769                                if( tmpShtNm != null ) { putConstSheet( tmpShtNm ); }
770                        }
771                }
772
773                /**
774                 * 読み取り時に、rowNo,colNo にあるセルの値を、固定値となるカラム名に関連付けます。
775                 *
776                 * イベントモデルでは、固定値の指定アドレス(rowNo,colNo)をピンポイントで取得することが
777                 * できないため、イベント発生毎に、チェックする必要があります。
778                 * そのため、固定値を使用すると、処理速度が低下します。
779                 *
780                 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
781                 *
782                 * @param   val     文字列値(null またはゼロ文字列は、不可)
783                 * @param   rowNo   行番号(0〜)
784                 * @param   colNo   列番号(0〜)
785                 */
786                void putConstValue( final String val,final int rowNo,final int colNo ) {
787                        if( rowNo <= maxRow ) {
788                                final String rowcol = rowNo + "-" + colNo ;
789                                final Integer adrs = rowcolMap.get( rowcol );
790                                if( adrs != null ) {
791                                        valsMap.put( adrs,val );
792                                }
793                        }
794                }
795
796                /**
797                 * ファイル系特殊記号(FILE,NAME,SUFIX)を指定します。
798                 *
799                 * ../AAA/BBB/CCC.XLS というファイルオブジェクトに対して、
800                 *   FILE    : CCC.XLS ファイル名
801                 *   NAME    : CCC     拡張子なしのファイル名
802                 *   SUFIX   : xls     ピリオド無しの拡張子(小文字に統一)
803                 *
804                 * これは、新しいファイルの読み取り開始時に、設定します。
805                 *
806                 * ※ rowcolMap を使用する為、必ず #setColumns( String... ) の実行後に行います。
807                 *
808                 * @og.rev 6.2.0.0 (2015/02/27) 新規作成
809                 *
810                 * @param   filNm  指定ファイル
811                 */
812                void putConstFile( final File filNm ) {
813                        if( filNm != null ) {
814                                tmpFile = filNm;                // 名前設定がされるまで、一時保管する。(順番があるので呼ばれればセットしておく)
815                                if( isNameSet ) {               // 名前設定がされないと、FILEやSHEET キーワードが使えない。
816                                        final FileInfo info = new FileInfo( filNm );
817
818                                        for( final String key : FileInfo.KEYS ) {
819                                                final Integer adrs = rowcolMap.get( key );
820                                                if( adrs != null ) {
821                                                        final String val = info.getValue( key );
822                                                        if( val != null ) {
823                                                                valsMap.put( adrs,val );
824                                                        }
825                                                }
826                                        }
827                                }
828                        }
829                }
830
831                /**
832                 * シート名を外部から指定します。
833                 * アドレス指定の特殊系として、"SHEET" 文字列を指定できます。
834                 * これは、新しいシートの読み取り開始時に、設定します。
835                 *
836                 * ※ rowcolMap を使用する為、必ず #setColumns( String... ) の実行後に行います。
837                 *
838                 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
839                 *
840                 * @param       shtNm   シート名
841                 */
842                void putConstSheet( final String shtNm ) {
843                        if( shtNm != null ) {
844                                tmpShtNm = shtNm;               // 名前設定がされるまで、一時保管する。
845                                if( isNameSet ) {               // 名前設定がされないと、FILEやSHEET キーワードが使えない。
846                                        final Integer adrs = rowcolMap.get( "SHEET" );
847                                        if( adrs != null ) {
848                                                valsMap.put( adrs,shtNm );
849                                        }
850                                }
851                        }
852                }
853
854                /**
855                 * 内部の valsMap を初期化します。
856                 * 固定値の読み取りは、シートごとに行います。
857                 * 新しいシートにデータが設定されていない場合、前のシートの値が残ります。
858                 * ここでは、シート呼出しごとに、毎回クリアします。
859                 * 引数に応じて、クリアする範囲を限定します。
860                 * END_FILE 時は、すべてクリア。END_SHEET 時は、ファイル情報は残します。
861                 *
862                 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
863                 *
864                 * @param   type 内部情報のクリア方法の指定(END_FILE or END_SHEET)
865                 */
866                void clearValsMap( final int type ) {
867                        valsMap.clear();
868                        if( type == END_SHEET ) { putConstFile( tmpFile ); }    // 全削除後にファイル情報を再設定
869                }
870
871                /**
872                 * 値配列のデータに、固定値を設定します。
873                 * 引数の文字列配列に、固定値を設定しますので、配列オブジェクト自体を更新します。
874                 *
875                 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
876                 *
877                 * @param   vals    文字列値の1行分の配列
878                 * @return  固定値を設定された1行分の文字列配列
879                 */
880                String[] getConstVals( final String[] vals ) {
881                        if( vals != null && vals.length > 0 ) {
882                                for( final Map.Entry<Integer,String> entry : valsMap.entrySet() ) {
883                                        final int adrs    = entry.getKey().intValue();                          // Autoボクシングでよい?
884                                        final String cnst = entry.getValue();
885                                        if( adrs < vals.length ) {
886                                                vals[adrs] = cnst;
887                                        }
888                                }
889                        }
890                        return vals ;
891                }
892        }
893}