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.plugin.io;
017
018import org.opengion.hayabusa.common.HybsSystem;
019import org.opengion.hayabusa.common.HybsSystemException;
020import org.opengion.hayabusa.db.DBTableModelUtil;
021import org.opengion.hayabusa.io.AbstractTableReader;
022import org.opengion.fukurou.util.StringUtil;
023
024import jxl.Workbook;
025import jxl.WorkbookSettings;
026import jxl.Sheet;
027import jxl.Cell;
028import jxl.read.biff.BiffException;
029
030import java.io.File;
031import java.io.BufferedReader;
032import java.io.IOException;
033
034/**
035 * 【廃止】JExcelによるEXCELバイナリファイルを読み取る実装クラスです。
036 *
037 * ファイル名、シート名を指定して、データを読み取ることが可能です。
038 * 第一カラムが # で始まる行は、コメント行なので、読み飛ばします。
039 * カラム名の指定行で、カラム名が null の場合は、その列は読み飛ばします。
040 * 
041 * ※POIを利用したTableReader_Excelを利用してください。
042 *
043 * @og.rev 3.5.4.8 (2004/02/23) 新規作成
044 * @og.group ファイル入力
045 *
046 * @version  4.0
047 * @author   Kazuhiko Hasegawa
048 * @since    JDK5.0,
049 */
050public class TableReader_JExcel extends AbstractTableReader {
051        //* このプログラムのVERSION文字列を設定します。   {@value} */
052        private static final String VERSION = "5.5.7.2 (2012/10/09)" ;
053
054        private String  sheetName               = null;         // 3.5.4.2 (2003/12/15)
055        private String  sheetNos                = null;         // 5.5.7.2 (2012/10/09)
056        private String  filename                = null;         // 3.5.4.3 (2004/01/05)
057
058        /**
059         * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。
060         * コメント/空行を除き、最初の行は、必ず項目名が必要です。
061         * それ以降は、コメント/空行を除き、データとして読み込んでいきます。
062         * このメソッドは、EXCEL 読み込み時に使用します。
063         *
064         * @og.rev 4.0.0.0 (2006/09/31) 新規追加
065         * @og.rev 5.1.6.0 (2010/05/01) columns 処理 追加
066         * @og.rev 5.1.6.0 (2010/05/01) skipRowCount , useNumber の追加
067         * @og.rev 5.2.1.0 (2010/10/01) setTableColumnValues メソッドを経由して、テーブルにデータをセットする。
068         * @og.rev 5.5.7.2 (2012/10/09) sheetNos 追加による複数シートのマージ読み取りサポート
069         *
070         * @see #isExcel()
071         */
072        @Override
073        public void readDBTable() {
074                Workbook wb = null;
075                try {
076                        WorkbookSettings settings = new WorkbookSettings();
077                        // System.gc()「ガベージコレクション」の実行をOFFに設定
078                        settings.setGCDisabled(true);
079                        wb = Workbook.getWorkbook(new File(filename),settings);
080
081//                      Sheet sheet ;
082                        Sheet[] sheets ;                                                                        // 5.5.7.2 (2012/10/09) 配列に変更
083
084                        // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。 sheetNos の指定が優先される。
085                        if( sheetNos != null && sheetNos.length() > 0 ) {
086                                String[] sheetList = StringUtil.csv2ArrayExt( sheetNos , wb.getNumberOfSheets()-1 );    // 最大シート番号は、シート数-1
087                                sheets = new Sheet[sheetList.length];
088                                for( int i=0; i<sheetList.length; i++ ) {
089                                        sheets[i] = wb.getSheet( Integer.parseInt( sheetList[i] ) );
090                                }
091                        }
092                        else if( sheetName != null && sheetName.length() > 0 ) {
093                                Sheet sheet = wb.getSheet( sheetName );
094                                if( sheet == null ) {
095                                        String errMsg = "対応するシートが存在しません。 Sheet=[" + sheetName + "]" ;
096                                        throw new HybsSystemException( errMsg );
097                                }
098                                sheets = new Sheet[] { sheet };
099                        }
100                        else {
101                                Sheet sheet = wb.getSheet(0);
102                                sheets = new Sheet[] { sheet };
103                        }
104
105//                      if( sheetName == null || sheetName.length() == 0 ) {
106//                              sheet = wb.getSheet(0);
107//                      }
108//                      else {
109//                              sheet = wb.getSheet( sheetName );
110//                              if( sheet == null ) {
111//                                      String errMsg = "対応するシートが存在しません。 Sheet=[" + sheetName + "]" ;
112//                                      throw new HybsSystemException( errMsg );
113//                              }
114//                      }
115
116                        boolean nameNoSet = true;
117                        table = DBTableModelUtil.newDBTable();
118
119                        int numberOfRows = 0;
120                        JxlHeaderData data = new JxlHeaderData();
121
122                        // 5.1.6.0 (2010/05/01) columns 処理
123                        data.setUseNumber( isUseNumber() );
124                        if( data.setColumns( columns ) ) {
125                                nameNoSet = false;
126                                table.init( data.getColumnSize() );
127                                setTableDBColumn( data.getNames() ) ;
128                        }
129
130                        // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。 
131                        for( int i=0; i<sheets.length; i++ ) {                       // 5.5.7.2 (2012/10/09) シート配列を処理します。
132                                Sheet sheet = sheets[i] ;                                       // 5.5.7.2 (2012/10/09)
133                                int rowCnt = sheet.getRows();
134                                int skip = getSkipRowCount();           // 5.1.6.0 (2010/05/01)
135        //                      for( int nIndexRow = 0; nIndexRow < rowCnt; nIndexRow++) {
136                                for( int nIndexRow = skip; nIndexRow < rowCnt; nIndexRow++) {
137                                        Cell[] cells = sheet.getRow( nIndexRow );
138                                        if( data.isSkip( cells ) ) { continue; }
139                                        if( nameNoSet ) {
140                                                nameNoSet = false;
141                                                table.init( data.getColumnSize() );
142                                                setTableDBColumn( data.getNames() ) ;
143                                        }
144
145                                        if( numberOfRows < getMaxRowCount() ) {
146                                                setTableColumnValues( data.toArray( cells ) );          // 5.2.1.0 (2010/10/01)
147        //                                      table.addColumnValues( data.toArray( cells ) );
148                                                numberOfRows ++ ;
149                                        }
150                                        else {
151                                                table.setOverflow( true );
152                                        }
153                                }
154
155                                // 最後まで、#NAME が見つから無かった場合
156                                if( nameNoSet ) {
157                                        String errMsg = "最後まで、#NAME が見つかりませんでした。"
158                                                                        + HybsSystem.CR
159                                                                        + "ファイルが空か、もしくは損傷している可能性があります。"
160                                                                        + HybsSystem.CR ;
161                                        throw new HybsSystemException( errMsg );
162                                }
163                        }
164                }
165                catch (IOException ex) {
166                        String errMsg = "ファイル読込みエラー[" + filename + "]"  ;
167                        throw new HybsSystemException( errMsg,ex );
168                }
169                catch (BiffException ex) {
170                        String errMsg = "ファイル読込みエラー。データ形式が不正です[" + filename + "]"  ;
171                        throw new HybsSystemException( errMsg,ex );
172                }
173                finally {
174                        if( wb != null ) { wb.close(); }
175                }
176        }
177
178        /**
179         * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。
180         * コメント/空行を除き、最初の行は、必ず項目名が必要です。
181         * それ以降は、コメント/空行を除き、データとして読み込んでいきます。
182         *
183         * @og.rev 3.5.4.3 (2004/01/05) 引数に、BufferedReader を受け取る要に変更します。
184         * @og.rev 4.0.0.0 (2006/09/31) UnsupportedOperationException を発行します。
185         *
186         * @param   reader 各形式のデータ(使用していません)
187         */
188        @Override
189        public void readDBTable( final BufferedReader reader ) {
190                String errMsg = "このクラスでは実装されていません。";
191                throw new UnsupportedOperationException( errMsg );
192        }
193
194        /**
195         * DBTableModelのデータとしてEXCELファイルを読み込むときのシート名を設定します。
196         * これにより、複数の形式の異なるデータを順次読み込むことや、シートを指定して
197         * 読み取ることが可能になります。
198         * sheetNos と sheetName が同時に指定された場合は、sheetNos が優先されます。エラーにはならないのでご注意ください。
199         * のでご注意ください。
200         *
201         * @og.rev 3.5.4.2 (2003/12/15) 新規追加
202         *
203         * @param   sheetName シート名
204         */
205        @Override
206        public void setSheetName( final String sheetName ) {
207                this.sheetName = sheetName;
208        }
209
210        /**
211         * EXCELファイルを読み込むときのシート番号を指定します(初期値:0)。
212         *
213         * EXCEL読み込み時に複数シートをマージして取り込みます。
214         * シート番号は、0 から始まる数字で表します。
215         * ヘッダーは、最初のシートのカラム位置に合わせます。(ヘッダータイトルの自動認識はありません。)
216         * よって、指定するシートは、すべて同一レイアウトでないと取り込み時にカラムのずれが発生します。
217         * 
218         * シート番号の指定は、カンマ区切りで、複数指定できます。また、N-M の様にハイフンで繋げることで、
219         * N 番から、M 番のシート範囲を一括指定可能です。また、"*" による、全シート指定が可能です。
220         * これらの組み合わせも可能です。( 0,1,3,5-8,10-* )
221         * ただし、"*" に関しては例外的に、一文字だけで、すべてのシートを表すか、N-* を最後に指定するかの
222         * どちらかです。途中には、"*" は、現れません。
223         * シート番号は、重複(1,1,2,2)、逆転(3,2,1) での指定が可能です。これは、その指定順で、読み込まれます。
224         * sheetNos と sheetName が同時に指定された場合は、sheetNos が優先されます。エラーにはならないのでご注意ください。
225         * このメソッドは、isExcel() == true の場合のみ利用されます。
226         * 
227         * 初期値は、0(第一シート) です。
228         *
229         * ※ このクラスでは実装されていません。
230         *
231         * @og.rev 5.5.7.2 (2012/10/09) 新規追加
232         *
233         * @param   sheetNos EXCELファイルのシート番号(0から始まる)
234         * @see         #setSheetName( String ) 
235         */
236        @Override
237        public void setSheetNos( final String sheetNos ) {
238                this.sheetNos = sheetNos;
239        }
240
241        /**
242         * このクラスが、EXCEL対応機能を持っているかどうかを返します。
243         *
244         * EXCEL対応機能とは、シート名のセット、読み込み元ファイルの
245         * Fileオブジェクト取得などの、特殊機能です。
246         * 本来は、インターフェースを分けるべきと考えますが、taglib クラス等の
247         * 関係があり、問い合わせによる条件分岐で対応します。
248         *
249         * @og.rev 3.5.4.3 (2004/01/05) 新規追加
250         *
251         * @return      EXCEL対応機能を持っているかどうか(常にtrue)
252         */
253        @Override
254        public boolean isExcel() {
255                return true;
256        }
257
258        /**
259         * 読み取り元ファイル名をセットします。(DIR + Filename)
260         * これは、EXCEL追加機能として実装されています。
261         *
262         * @og.rev 3.5.4.3 (2004/01/05) 新規作成
263         *
264         * @param   filename 読み取り元ファイル名
265         */
266        @Override
267        public void setFilename( final String filename ) {
268                this.filename = filename;
269                if( filename == null ) {
270                        String errMsg = "ファイル名が指定されていません。" ;
271                        throw new HybsSystemException( errMsg );
272                }
273        }
274}
275
276/**
277 * EXCEL ネイティブのデータを処理する ローカルクラスです。
278 * このクラスでは、コメント行のスキップ判定、ヘッダー部のカラム名取得、
279 * 行情報(HSSFRow)から、カラムの配列の取得などを行います。
280 *
281 * @og.rev 3.5.4.8 (2004/02/23) 新規追加
282 * @og.group ファイル入力
283 *
284 * @version  4.0
285 * @author   儲
286 * @since    JDK5.0,
287 */
288class JxlHeaderData {
289        private String[] names ;
290        private int[]    index;
291        private int              columnSize = 0;
292        private boolean  nameNoSet = true;
293        private boolean  useNumber = true;
294
295        /**
296         * 行番号情報を使用するかどうか[true/false]を指定します(初期値:true)。
297         *
298         * 初期値は、true(使用する) です。
299         *
300         * @og.rev 5.1.6.0 (2010/05/01) 新規作成
301         *
302         * @param useNumber 行番号情報 [true:使用している/false:していない]
303         */
304        void setUseNumber( final boolean useNumber ) {
305                this.useNumber = useNumber ;
306        }
307
308        /**
309         * カラム名を外部から指定します。
310         * カラム名が、NULL でなければ、#NAME より、こちらが優先されます。
311         * カラム名は、順番に、指定する必要があります。
312         *
313         * @og.rev 5.1.6.0 (2010/05/01) 新規作成
314         *
315         * @param       columns EXCELのカラム列(CSV形式)
316         *
317         * @return true:処理実施/false:無処理
318         */
319        boolean setColumns( final String columns ) {
320                if( columns != null && columns.length() > 0 ) {
321                        names = StringUtil.csv2Array( columns );
322                        columnSize = names.length ;
323                        index = new int[columnSize];
324                        int adrs = (useNumber) ? 1:0 ;  // useNumber =true の場合は、1件目(No)は読み飛ばす。
325                        for( int i=0; i<columnSize; i++ ) { index[i] = adrs++; }
326                        nameNoSet = false;
327
328                        return true;
329                }
330                return false;
331        }
332
333        /**
334         * EXCEL ネイティブのデータを処理する ローカルクラスです。
335         * このクラスでは、コメント行のスキップ判定、ヘッダー部のカラム名取得、
336         * 行情報(HSSFRow)から、カラムの配列の取得などを行います。
337         *
338         * @param cells Cell[] EXCELのセル配列(行)
339         *
340         * @return true:コメント行/false:通常行
341         */
342        boolean isSkip( final Cell[] cells ) {
343                int size = cells.length ;
344                if( size == 0 ) { return true; }
345
346                String strText =  cells[0].getContents();
347                if( strText != null && strText.length() > 0 ) {
348                        if( nameNoSet ) {
349                                if( strText.equalsIgnoreCase( "#Name" ) ) {
350                                        makeNames( cells );
351                                        nameNoSet = false;
352                                        return true;
353                                }
354                                else if( strText.charAt( 0 ) == '#' ) {
355                                        return true;
356                                }
357                                else {
358                                        String errMsg = "#NAME が見つかる前にデータが見つかりました。"
359                                                                        + HybsSystem.CR
360                                                                        + "可能性として、ファイルが、ネイティブExcelでない事が考えられます。"
361                                                                        + HybsSystem.CR ;
362                                        throw new HybsSystemException( errMsg );
363                                }
364                        }
365                        else {
366                                if( strText.charAt( 0 ) == '#' ) {
367                                        return true;
368                                }
369                        }
370                }
371
372                return nameNoSet ;
373        }
374
375        /**
376         * EXCEL ネイティブの行のセル配列からカラム名情報を取得します。
377         *
378         * @og.rev 5.1.6.0 (2010/05/01) useNumber(行番号情報を、使用している(true)/していない(false)を指定)
379         *
380         * @param cells Cell[] EXCELの行のセル配列
381         */
382        private void makeNames( final Cell[] cells ) {
383                int maxCnt = cells.length;
384                String[] names2 = new String[maxCnt];
385                int[]    index2 = new int[maxCnt];
386
387                // 先頭カラムは、#NAME 属性行である。
388//              for( int nIndexCell = 1; nIndexCell < maxCnt; nIndexCell++) {
389                // 先頭カラムは、#NAME 属性行であるかどうかを、useNumber で判定しておく。
390                int nFirstCell = (useNumber) ? 1:0 ;
391                for( int nIndexCell = nFirstCell; nIndexCell < maxCnt; nIndexCell++) {
392                        String strText = cells[nIndexCell].getContents();
393
394                        if( strText != null && strText.length() > 0 ) {
395                                names2[columnSize] = strText;
396                                index2[columnSize] = nIndexCell;
397                                columnSize++;
398                        }
399                }
400
401                // #NAME を使用しない場合:no欄が存在しないケース
402                if( maxCnt == columnSize ) {
403                        names = names2;
404                        index = index2;
405                }
406                else {
407                        names = new String[columnSize];
408                        index = new int[columnSize];
409                        System.arraycopy(names2, 0, names, 0, columnSize);
410                        System.arraycopy(index2, 0, index, 0, columnSize);
411                }
412        }
413
414        /**
415         * カラム名情報を返します。
416         * ここでは、内部配列をそのまま返します。
417         *
418         * @return String[] カラム列配列情報
419         */
420        String[] getNames() {
421                return names;
422        }
423
424        /**
425         * カラムサイズを返します。
426         *
427         * @return      カラムサイズ
428         */
429        int getColumnSize() {
430                return columnSize;
431        }
432
433        /**
434         * カラム名情報を返します。
435         *
436         * @param cells Cell[] EXCELの行のセル配列
437         *
438         * @return String[] カラム列配列情報
439         */
440        String[] toArray( final Cell[] cells ) {
441                if( nameNoSet ) {
442                        String errMsg = "#NAME が見つかる前にデータが見つかりました。";
443                        throw new HybsSystemException( errMsg );
444                }
445
446                int cellSize = cells.length;
447                String[] data = new String[columnSize];
448                for( int i=0;i<columnSize; i++ ) {
449                        int indx = index[i];
450                        if( indx < cellSize ) {
451                                data[i] = cells[indx].getContents();
452                        }
453                        else {
454                                data[i] = null;
455                        }
456                }
457
458                return data;
459        }
460}