001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.hayabusa.report;
017
018import org.opengion.fukurou.system.OgRuntimeException ;                 // 6.4.2.0 (2016/01/29)
019import org.opengion.hayabusa.common.HybsSystemException;
020import org.opengion.fukurou.system.Closer ;
021import static org.opengion.fukurou.system.HybsConst.CR ;                // 6.1.0.0 (2014/12/26)
022import static org.opengion.fukurou.system.HybsConst.FS ;                // 6.1.0.0 (2014/12/26)
023
024import org.apache.poi.poifs.filesystem.POIFSFileSystem;
025import org.apache.poi.hssf.usermodel.HSSFWorkbook;
026import org.apache.poi.hssf.usermodel.HSSFSheet;
027import org.apache.poi.hssf.usermodel.HSSFRow;
028import org.apache.poi.hssf.usermodel.HSSFCell;
029import org.apache.poi.hssf.usermodel.HSSFRichTextString;
030
031import java.io.FileInputStream;
032import java.io.FileOutputStream;
033import java.io.IOException;
034
035import java.util.regex.Pattern;
036import java.util.regex.Matcher;
037
038/**
039 * DBTableReport インターフェース を実装したネイティブEXCEL形式で出力するクラスです。
040 * AbstractDBTableReport を継承していますので,writeReport() のみオーバーライドして,
041 * 固定長文字ファイルの出力機能を実現しています。
042 *
043 * @og.group 帳票システム
044 *
045 * @version  4.0
046 * @author   Kazuhiko Hasegawa
047 * @since    JDK5.0,
048 */
049public class DBTableReport_Excel extends AbstractDBTableReport {
050
051        private static final String EXCEL_FILE_EXT        = ".xls";
052        private static final Pattern PATTERN_KEY =
053                          Pattern.compile("\\{@((\\w+?)(?:_(\\d+?))?)\\}", Pattern.MULTILINE);
054
055        // POIの解析した式の中に変な属性が付けられて、これを取り除く(patternExcludeInFormula)
056        private static final Pattern PATTERN_EXIN =
057                          Pattern.compile("ATTR\\(semiVolatile\\)", Pattern.MULTILINE);
058
059        private HSSFWorkbook wb ;
060
061        /**
062         * デフォルトコンストラクター
063         *
064         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
065         */
066        public DBTableReport_Excel() { super(); }               // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
067
068        /**
069         * DBTableModel から データを作成して,PrintWriter に書き出します。
070         *
071         */
072        @Override
073        public void writeReport() {
074                setHeaderFooter();
075                initReader();
076                initWriter();
077                changeSheet();
078                close();
079        }
080
081        /**
082         * POIFSFileSystem を、初期化します。
083         * これは、雛型ファイルの終端まで読取り、処理した場合、もう一度
084         * 初めから読み込みなおす処理を行います。
085         * 基本的に、書き込みも初期化する必要があります。
086         *
087         * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。
088         *
089         */
090        @Override
091        protected void initReader() {
092                if( null != wb ) { wb = null; }
093
094                FileInputStream  istream = null;
095                try {
096                        istream = new FileInputStream(templateFile);
097                        final POIFSFileSystem fs = new POIFSFileSystem(istream);
098                        wb = new HSSFWorkbook(fs);
099                }
100                catch( final IOException ex ) {
101                        final String errMsg = "ファイル名がオープン出来ませんでした。"
102                                                + CR
103                                                + "  File:" + templateFile;
104                        throw new HybsSystemException( errMsg,ex );             // 3.5.5.4 (2004/04/15) 引数の並び順変更
105                }
106                finally {
107                        Closer.ioClose( istream );      // 4.0.0 (2006/01/31) close 処理時の IOException を無視
108                }
109        }
110
111        /**
112         * FileOutputStream を、初期化します。
113         * これは、雛型ファイルを終端まで読取り、処理した場合、出力ファイル名を
114         * 変えて、別ファイルとして出力する為のものです。
115         * 基本的に、読取も初期化する必要があります。
116         *
117         * 現在の所、POIはメモリ上にExcelファイルを作成する為、作成したファイルの書く込むを
118         * ファイル閉じる時点に伸ばされます。
119         *
120         */
121        @Override
122        protected void initWriter() {
123                // ここでは処理を行いません。
124        }
125
126        /**
127         * リーダー、ライターの終了処理を行います。
128         * このメソッドが呼ばれたタイミングで、実際にファイル出力を行います。
129         *
130         * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
131         *
132         */
133        protected void close() {
134                // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
135                if( wb == null ) {
136                        final String errMsg = "#initReader()を先に実行しておいてください。" ;
137                        throw new OgRuntimeException( errMsg );
138                }
139
140                final String filename = htmlDir + FS + htmlFileKey + EXCEL_FILE_EXT ;
141
142                FileOutputStream fileOut = null;
143                try {
144                        // Write the output to a file
145                        fileOut = new FileOutputStream(filename);
146                        wb.write(fileOut);
147                }
148                catch( final IOException ex ) {
149                        wb = null;
150                        final String errMsg = "ファイルが出力(書き込み)出来ませんでした。"
151                                                + CR
152                                                + "  File:" + filename;
153                        throw new HybsSystemException( errMsg,ex );             // 3.5.5.4 (2004/04/15) 引数の並び順変更
154                }
155                finally {
156                        Closer.ioClose( fileOut );      // 4.0.0 (2006/01/31) close 処理時の IOException を無視
157                }
158        }
159
160        /**
161         * Excelの雛型をコピーして、そのシートに帳票データを埋め込みます。
162         * いろいろな属性がある所に、適切に対応していく予定。
163         * 各サブクラスで実装してください。
164         *
165         * @og.rev 4.3.4.0 (2008/12/01) POI3.2対応
166         * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
167         *
168         */
169        protected void changeSheet() {
170                // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
171                if( wb == null ) {
172                        final String errMsg = "#initReader()を先に実行しておいてください。" ;
173                        throw new OgRuntimeException( errMsg );
174                }
175
176                final HSSFSheet patternSheet = wb.getSheetAt(0);
177                while(!rowOver) {
178                        final HSSFSheet sheet2 = wb.cloneSheet(0);
179        //              HSSFRow oRow2;
180                        final int nFirstRow = sheet2.getFirstRowNum();
181                        final int nLastRow  = sheet2.getLastRowNum();
182        //              int nTotalRows = patternSheet.getPhysicalNumberOfRows();
183                        for( int nIndexRow=nFirstRow; nIndexRow<=nLastRow; nIndexRow++ ) {
184                                final HSSFRow oRow = patternSheet.getRow(nIndexRow);
185                                if( null != oRow ) {
186        //                              int nTotalCells = oRow.getPhysicalNumberOfCells();
187                                        // 4.3.4.0 (2008/12/01) POI3.2対応。shortをintにする。
188                                        // short nFirstCell = oRow.getFirstCellNum();
189                                        // short nLastCell  = oRow.getLastCellNum();
190                                        final int nFirstCell = oRow.getFirstCellNum();
191                                        final int nLastCell  = oRow.getLastCellNum();
192                                        for( int nIndexCell=nFirstCell; nIndexCell<=nLastCell; nIndexCell++ ) {
193                                                final HSSFCell oCell = oRow.getCell(nIndexCell);
194                                                if( null != oCell ) { changeCell(oCell);}
195                                        }
196                                }
197                        }
198                }
199        }
200
201        /**
202         * セル情報を変更します。
203         *
204         * @og.rev 4.3.4.0 (2008/12/01) POI3.2対応
205         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Hyperlink.LINK_XXXX → HyperlinkType.XXXX)
206         *
207         * @param oCell HSSFCellオブジェクト
208         */
209        @SuppressWarnings(value={"deprecation"})        // poi-3.15
210        protected void changeCell(final HSSFCell oCell) {
211                String strText;
212                HSSFRichTextString richText;
213        //      final int nCellType = oCell.getCellType();
214        //      switch(nCellType) {
215                switch( oCell.getCellTypeEnum() ) {
216        //              case HSSFCell.CELL_TYPE_FORMULA:                                                                        // 6.5.0.0 (2016/09/30) poi-3.12
217                        case FORMULA:                                                                                                           // 6.5.0.0 (2016/09/30) poi-3.15
218                                strText = changeData(changeFormulaAttr(oCell.getCellFormula()));
219                                if( null != strText ) {
220        //                              oCell.setCellType(HSSFCell.CELL_TYPE_FORMULA);
221        //                              oCell.setEncoding(HSSFCell.ENCODING_UTF_16);
222                                        oCell.setCellFormula(strText);
223                                }
224                                break;
225        //              case HSSFCell.CELL_TYPE_STRING:                                                                         // 6.5.0.0 (2016/09/30) poi-3.12
226                        case STRING:                                                                                                            // 6.5.0.0 (2016/09/30) poi-3.15
227        // POI3.0       strText =  changeData(oCell.getStringCellValue());
228                                richText = oCell.getRichStringCellValue();
229                                strText =  changeData(richText.getString());
230                                if( null != strText ) {
231        //                              oCell.setCellType(HSSFCell.CELL_TYPE_STRING);
232        // POI3.0               oCell.setEncoding(HSSFCell.ENCODING_UTF_16);
233        // POI3.2               oCell.setCellValue( strText );  // POI3.0 Deprecation
234                                        oCell.setCellValue( new HSSFRichTextString(strText) );
235                                }
236                                break;
237        //              case HSSFCell.CELL_TYPE_NUMERIC:
238        //                      break;
239        //              case HSSFCell.CELL_TYPE_BOOLEAN:
240        //                      break;
241        //              case HSSFCell.CELL_TYPE_ERROR:
242        //                      break;
243                        default :
244                                break;
245                }
246        }
247
248        /**
249         * POIで解釈したExcel式の中の変な属性を加工して、出力します。
250         * いろいろな属性がある所に、適切に対応していく予定。
251         * 各サブクラスで実装してください。
252         *
253         * @param       inLine  入力文字列
254         *
255         * @return      出力文字列
256         */
257        protected String changeFormulaAttr( final String inLine ) {
258                // rowOver で、かつ ページブレークかページエンドカットの場合、処理終了。
259                final Matcher  matcher = PATTERN_EXIN.matcher( inLine );
260                return matcher.find() ? matcher.replaceAll( "" ) : inLine;
261        }
262
263        /**
264         * 入力文字列 を加工して、出力します。
265         * データをテーブルモデルより読み取り、値をセットします。
266         * 各サブクラスで実装してください。
267         *
268         * @param       inLine  入力文字列
269         *
270         * @return      出力文字列. 文字列の変換は要らない場合、nullを返します
271         */
272        @Override
273        protected String changeData( final String inLine ) {
274                boolean bFind = false;
275
276                // rowOver で、かつ ページブレークかページエンドカットの場合、処理終了。
277                final Matcher  matcher = PATTERN_KEY.matcher( inLine );
278                final StringBuffer sb = new StringBuffer();     // Matcher.appendTail( StringBuffer ) の為
279
280                while( matcher.find() ) {
281                        matcher.appendReplacement( sb, getValue( matcher.group( 1 ) ) );
282                        bFind = true;
283                }
284
285                if( bFind ) {
286                        matcher.appendTail( sb );
287                        return sb.toString();
288                }
289                else {
290                        return null;
291                }
292        }
293
294        /**
295         * 入力文字列 を読み取って、出力します。
296         * tr タグを目印に、1行(trタグ間)ずつ取り出します。
297         * 読み取りを終了する場合は、null を返します。
298         * 各サブクラスで実装してください。
299         * ※ このクラスでは実装されていません。
300         *
301         * @return      出力文字列
302         */
303        @Override
304        protected String readLine() {
305                throw new UnsupportedOperationException();
306        }
307
308        /**
309         * 入力文字列 を読み取って、出力します。
310         * 各サブクラスで実装してください。
311         * ※ このクラスでは実装されていません。
312         *
313         * @param line 入力文字列
314         */
315        @Override
316        protected void println( final String line ) {
317                throw new UnsupportedOperationException();
318        }
319}