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.hayabusa.common.HybsSystemException; 019 020import java.util.Map; 021import java.util.HashMap; 022import java.util.List; 023import java.util.ArrayList; 024import java.util.Iterator ; 025import java.util.NoSuchElementException; 026import java.util.Arrays ; 027 028/** 029 * 【EXCEL取込】雛形EXCELシートの {@カラム} 解析データを管理、収集する 雛形レイアウト管理クラスです。 030 * POIのHSSFListener などで、雛形情報を収集し、HSSFSheet などで、雛形情報のアドレス(行列)から 031 * 必要な情報を取得し、このオブジェクトに設定しておきます。 032 * EXCELシート毎に、INSERT文と、対応する文字列配列を取り出します。 033 * 034 * @og.rev 3.8.0.0 (2005/06/07) 新規追加 035 * @og.group 帳票システム 036 * 037 * @version 4.0 038 * @author Kazuhiko Hasegawa 039 * @since JDK5.0, 040 */ 041public class ExcelLayout { 042 043 private final Map<String,String> headMap = new HashMap<String,String>(); // シート単位のヘッダーキーを格納します。 044 private final Map<String,String> bodyMap = new HashMap<String,String>(); // シート単位のボディーキーを格納します。 045 private final Map<Integer,Map<String,String>> dataMap = new HashMap<Integer,Map<String,String>>(); // シート単位のデータを格納するMapを格納します。(キーは、GEEDNO) 046 047 private final List<ExcelLayoutData>[] model ; // シート毎にExcelLayoutDataが格納されます。 048 049 private String loopClm = null; // 繰返必須カラム(なければnull)) 050 private ExcelLayoutDataIterator iterator = null; // ExcelLayoutData を返す、Iterator 051 052 /** 053 * コンストラクター 054 * 055 * 雛形の最大シート数を設定します。 056 * ここでは、連番で管理している為、その雛形シート番号が処理対象外であっても、 057 * 雛形EXCEL上に存在するシート数を設定する必要があります。 058 * 具体的には、HSSFListener#processRecord( Record )で、BoundSheetRecord.sid の 059 * イベントの数を数えて設定します。 060 * 061 * @param sheetSize 最大シート数 062 */ 063 @SuppressWarnings(value={"unchecked","rawtypes"}) 064 public ExcelLayout( final int sheetSize ) { 065 model = new ArrayList[sheetSize]; 066 for( int i=0; i<sheetSize; i++ ) { 067 model[i] = new ArrayList<ExcelLayoutData>(); 068 } 069 } 070 071 /** 072 * 雛形EXCELの {@カラム} 解析情報を設定します。 073 * 074 * 雛形EXCELは、HSSFListener を使用して、イベント駆動で取得します。その場合、 075 * {@カラム}を含むセルを見つける都度、このメソッドを呼び出して、{@カラム}の 076 * 位置(行列番号)を設定します。 077 * データEXCELからデータを読み出す場合は、ここで登録したカラムの行列より、読み込みます。 078 * 具体的には、HSSFListener#processRecord( Record )で、SSTRecord.sid の 情報をキープしておき、 079 * LabelSSTRecord.sid 毎に、{@カラム}を含むかチェックし、含む場合に、このメソッドに 080 * 解析情報を設定します。 081 * 082 * @param sheetNo シート番号 083 * @param key 処理カラム 084 * @param rowNo 行番号 085 * @param colNo 列番号 086 */ 087 public void addModel( final int sheetNo, final String key, final int rowNo, final short colNo ) { 088 model[sheetNo].add( new ExcelLayoutData( key,rowNo,colNo ) ); 089 } 090 091 /** 092 * 雛形EXCELの {@カラム} 解析情報(ExcelLayoutData)を配列で取得します。 093 * 094 * 雛形EXCELは、イベント処理で取り込む為、すべての処理が終了してから、このメソッドで 095 * 処理結果を取り出す必要があります。 096 * 解析情報は、ExcelLayoutData オブジェクトにシート単位に保管されています。 097 * この ExcelLayoutData オブジェクト ひとつに、{@カラム} ひとつ、つまり、 098 * ある特定の行列番号を持っています。 099 * データEXCELを読取る場合、この ExcelLayoutData配列から、行列情報を取り出し、 100 * addData メソッドで、キー情報と関連付けて登録する為に、使用します。 101 * 102 * @param sheetNo シート番号 103 * @param loopClm 繰返必須カラム(なければ通常の1対1処理) 104 * 105 * @return ExcelLayoutData配列 106 */ 107 public Iterator<ExcelLayoutData> getLayoutDataIterator( final int sheetNo, final String loopClm ) { 108 this.loopClm = loopClm ; 109 ExcelLayoutData[] datas = model[sheetNo].toArray( new ExcelLayoutData[model[sheetNo].size()] ); 110 iterator = new ExcelLayoutDataIterator( datas,loopClm ); 111 return iterator ; 112 } 113 114 /** 115 * 解析情報(clm,edbn)と関連付けて、データEXCELの値を設定します。 116 * 117 * データEXCELは、雛形EXCELの解析情報を元に、行列番号から設定値を取り出します。 118 * その設定値は、取りだした ExcelLayoutData の clm,edbn と関連付けて、このメソッドで登録します。 119 * この処理は、シート毎に、初期化して使う必要があります。 120 * 初期化メソッドする場合は、dataClear() を呼び出してください。 121 * 122 * @param clm カラム名 123 * @param edbn 枝番 124 * @param value データ値 125 */ 126 public void addData( final String clm, final int edbn, final String value ) { 127 if( loopClm != null && loopClm.equals( clm ) && edbn >= 0 && ( value == null || value.length() == 0 ) ) { 128 iterator.setEnd(); 129 Integer edbnObj = Integer.valueOf( edbn ); 130 dataMap.remove( edbnObj ); // 枝番単位のMapを削除 131 return ; 132 } 133 134 Integer edbnObj = Integer.valueOf( edbn ); 135 Map<String,String> map = dataMap.get( edbnObj ); // 枝番単位のMapを取得 136 if( map == null ) { map = new HashMap<String,String>(); } 137 map.put( clm,value ); // 枝番に含まれるキーと値をセット 138 dataMap.put( edbnObj,map ); // そのMapを枝番に登録 139 140 if( edbn < 0 ) { 141 headMap.put( clm,null ); 142 } 143 else { 144 bodyMap.put( clm,null ); 145 } 146 } 147 148 /** 149 * データEXCELの設定情報を初期化します。 150 * 151 * データEXCELと、雛形EXCELの解析情報を関連付ける処理は、シート毎に行う必要があります。 152 * 処理終了時(シート切り替え時)このメソッドを呼び出して、初期化しておく必要があります 153 * 154 */ 155 public void dataClear() { 156 dataMap.clear(); 157 headMap.clear(); 158 bodyMap.clear(); 159 } 160 161 /** 162 * ヘッダー情報のINSERT用Query文字列を取得します。 163 * 164 * シート単位に、データEXCELより、INSERT用のQuery文字列を作成します。 165 * この、Query は、シート単位に登録したキー情報の最大数(使用されているすべてのキー)を 166 * 元に、PreparedStatement で処理できる形の INSERT文を作成します。 167 * シート単位に呼び出す必要があります。 168 * 169 * @param table ヘッダー情報を登録するデータベース名(HEADERDBID) 170 * 171 * @return ヘッダー情報のINSERT用Query文字列 172 */ 173 public String getHeaderInsertQuery( final String table ) { 174 if( table == null || table.length() == 0 || headMap.isEmpty() ) { return null; } 175 return makeQuery( table,headMap ); 176 } 177 178 /** 179 * ボディ(明細)情報のINSERT用Query文字列を取得します。 180 * 181 * シート単位に、データEXCELより、INSERT用のQuery文字列を作成します。 182 * この、Query は、シート単位に登録したキー情報の最大数(使用されているすべてのキー)を 183 * 元に、PreparedStatement で処理できる形の INSERT文を作成します。 184 * シート単位に呼び出す必要があります。 185 * 186 * @param table ボディ(明細)情報を登録するデータベース名(BODYDBID) 187 * 188 * @return ボディ(明細)情報のINSERT用Query文字列 189 */ 190 public String getBodyInsertQuery( final String table ) { 191 if( table == null || table.length() == 0 || bodyMap.isEmpty() ) { return null; } 192 return makeQuery( table,bodyMap ); 193 } 194 195 /** 196 * ヘッダー情報のINSERT用Queryに対応する、データ配列を取得します。 197 * 198 * getHeaderInsertQuery( String ) で取りだした PreparedStatement に設定する値配列です。 199 * シート単位に呼び出す必要があります。 200 * 201 * @param systemId システムID(SYSTEM_ID) 202 * @param ykno 要求番号(YKNO) 203 * @param sheetNo 登録するデータEXCELのシート番号(SHEETNO) 204 * 205 * @return データ配列 206 */ 207 public String[] getHeaderInsertData( final String systemId,final int ykno,final int sheetNo ) { 208 String[] keys = headMap.keySet().toArray( new String[headMap.size()] ); 209 if( keys == null || keys.length == 0 ) { return new String[0]; } 210 211 Integer edbnObj = Integer.valueOf( -1 ); // ヘッダー 212 Map<String,String> map = dataMap.get( edbnObj ); 213 if( map == null ) { return new String[0]; } 214 215 String[] rtnData = new String[keys.length+4]; 216 217 rtnData[0] = systemId; 218 rtnData[1] = String.valueOf( ykno ); 219 rtnData[2] = String.valueOf( sheetNo ); 220 rtnData[3] = String.valueOf( -1 ); // 枝番 221 222 for( int i=0; i<keys.length; i++ ) { 223 rtnData[i+4] = map.get( keys[i] ); 224 } 225 226 return rtnData; 227 } 228 229 /** 230 * ボディ(明細)情報のINSERT用Queryに対応する、データ配列のリスト(String[] のList)を取得します。 231 * 232 * getHeaderInsertQuery( String ) で取りだした PreparedStatement に設定する値配列です。 233 * シート単位に呼び出す必要があります。 234 * 235 * @param systemId システムID(SYSTEM_ID) 236 * @param ykno 要求番号(YKNO) 237 * @param sheetNo 登録するデータEXCELのシート番号(SHEETNO) 238 * 239 * @return データ配列のリスト 240 */ 241 public List<String[]> getBodyInsertData( final String systemId,final int ykno,final int sheetNo ) { 242 String[] keys = bodyMap.keySet().toArray( new String[bodyMap.size()] ); 243 if( keys == null || keys.length == 0 ) { return null; } 244 245 List<String[]> rtnList = new ArrayList<String[]>(); 246 247 Integer[] edbnObjs = dataMap.keySet().toArray( new Integer[dataMap.size()] ); 248 for( int i=0; i<edbnObjs.length; i++ ) { 249 int edbn = edbnObjs[i].intValue(); 250 if( edbn < 0 ) { continue; } // ヘッダーの場合は、読み直し 251 252 String[] rtnData = new String[keys.length+4]; // 毎回、新規に作成する。 253 rtnData[0] = systemId; 254 rtnData[1] = String.valueOf( ykno ); 255 rtnData[2] = String.valueOf( sheetNo ); 256 rtnData[3] = String.valueOf( edbn ); // 枝番 257 258 Map<String,String> map = dataMap.get( edbnObjs[i] ); 259 for( int j=0; j<keys.length; j++ ) { 260 rtnData[j+4] = map.get( keys[j] ); 261 } 262 rtnList.add( rtnData ); 263 } 264 265 return rtnList; 266 } 267 268 /** 269 * 内部情報Mapより、INSERT用Query文字列を取得します。 270 * 271 * シート単位に、データEXCELより、INSERT用のQuery文字列を作成します。 272 * この、Query は、シート単位に登録したキー情報の最大数(使用されているすべてのキー)を 273 * 元に、PreparedStatement で処理できる形の INSERT文を作成します。 274 * シート単位に呼び出す必要があります。 275 * 276 * @param table テーブル名 277 * @param map ボディ(明細)情報を登録する内部情報Map 278 * 279 * @return INSERT用Query文字列 280 */ 281 private String makeQuery( final String table,final Map<String,String> map ) { 282 String[] keys = map.keySet().toArray( new String[map.size()] ); 283 284 if( keys == null || keys.length == 0 ) { return null; } 285 286 StringBuilder buf1 = new StringBuilder( 200 ); 287 buf1.append( "INSERT INTO " ).append( table ).append( " ( " ); 288 buf1.append( "GESYSTEM_ID,GEYKNO,GESHEETNO,GEEDNO" ); 289 290 StringBuilder buf2 = new StringBuilder( 200 ); 291 buf2.append( " ) VALUES (" ); 292 buf2.append( "?,?,?,?" ); 293 294 for( int i=0; i<keys.length; i++ ) { 295 buf1.append( "," ).append( keys[i] ); 296 buf2.append( ",?" ); 297 } 298 buf2.append( ")" ); 299 300 buf1.append( buf2 ); 301 302 return buf1.toString(); 303 } 304} 305 306/** 307 * ExcelLayoutData (雛形解析結果)のシート毎のIteratorを返します。 308 * ExcelLayout では、データEXCELは、シート毎に解析します。 309 * 通常は、雛形とデータは1対1の関係で、雛形より多いデータは、読み取りませんし、 310 * 少ないデータは、NULL値でデータ登録します。 311 * ここで、繰返必須カラム(LOOPCLM)を指定することで、指定のカラムが必須であることを利用して、 312 * データが少ない場合は、そこまでで処理を中止して、データが多い場合は、仮想的にカラムが 313 * 存在すると仮定して、雛形に存在しない箇所のデータを読み取れるように、Iterator を返します。 314 * データがオーバーする場合は、仮想的にカラムの存在するアドレスを求める必要があるため、 315 * 最低 カラム_0 と カラム_1 が必要です。さらに、各カラムは、行方向に並んでおり、 316 * 列方向は、同一であるという前提で、読み取るべき行列番号を作成します。 317 * 318 * @og.rev 3.8.0.0 (2005/06/07) 新規追加 319 * @og.group 帳票システム 320 * 321 * @version 4.0 322 * @author Kazuhiko Hasegawa 323 * @since JDK5.0, 324 */ 325class ExcelLayoutDataIterator implements Iterator<ExcelLayoutData> { 326 private final ExcelLayoutData[] layoutDatas ; 327 private final String loopClm ; 328 private int incSize = 1; // 行番号の増加数(段組などの場合は、1以上となる) 329 private int count = 0; // 現在処理中の行番号 330 private int edbnCnt = 0; // 処理中の枝番に相当するカウント値 331 private int stAdrs = -1; // 繰返し処理を行う開始アドレス 332 private int edAdrs = -1; // 繰返し処理を行う終了アドレス 333 334 /** 335 * ExcelLayoutData の配列を受け取って、初期情報を設定します。 336 * 337 * 繰返必須カラム(LOOPCLM)がnullでない場合、枝番が0のカラムを繰り返します。 338 * 繰り返す場合、行番号と枝番を指定して、既存のExcelLayoutDataオブジェクトを作成し、 339 * 仮想的に繰返します。 340 * 341 * @param datas ExcelLayoutData[] ExcelLayoutData の配列 342 * @param lpClm 繰返必須カラム(LOOPCLM) 343 */ 344 public ExcelLayoutDataIterator( final ExcelLayoutData[] datas,final String lpClm ) { 345 layoutDatas = datas; 346 loopClm = lpClm; 347 348 int size = layoutDatas.length; // 配列の最大値 349 350 Arrays.sort( layoutDatas ); // 枝番順にソートされます。 351 // loopClm を使う場合は、枝番 -1(ヘッダ)と、0のデータのみを使用する。枝番1は、増加数の取得のみに用いる。 352 if( loopClm != null ) { 353 int zeroRow = -1; 354 for( int i=0; i<size; i++ ) { 355 // System.out.println( "count=" + i + ":" + layoutDatas[i] ); 356 int edbn = layoutDatas[i].getEdbn(); 357 if( stAdrs < 0 && edbn == 0 ) { stAdrs = i; } // 初の枝番0アドレス=開始(含む) 358 if( edAdrs < 0 && edbn == 1 ) { edAdrs = i; } // 初の枝番1アドレス=終了(含まない) 359 if( loopClm.equals( layoutDatas[i].getClm() ) ) { 360 if( edbn == 0 ) { 361 zeroRow = layoutDatas[i].getRowNo(); // loopClm の枝番0 の行番号 362 } 363 else if( edbn == 1 ) { 364 incSize = layoutDatas[i].getRowNo() - zeroRow; // 増加数=枝番1-枝番0 365 break; 366 } 367 } 368 } 369 // 繰返がある場合(枝番が0以上)でloopClmが見つからない場合はエラー 370 if( zeroRow < 0 && stAdrs >= 0 ) { 371 String errMsg = "繰返必須カラムがシート中に存在しません。[" + loopClm + "]"; 372 throw new HybsSystemException( errMsg ); 373 } 374 } 375 if( stAdrs < 0 ) { stAdrs = 0; } // 開始(含む) 376 if( edAdrs < 0 ) { edAdrs = size; } // 終了(含まない) 377// System.out.println( "stAdrs=" + stAdrs + " , edAdrs=" + edAdrs ); 378 } 379 380 /** 381 * 繰り返し処理でさらに要素がある場合に true を返します。 382 * つまり、next が例外をスローしないで要素を返す場合に true を返します。 383 * 384 * @return 反復子がさらに要素を持つ場合は true 385 */ 386 public boolean hasNext() { 387 if( loopClm != null && count == edAdrs ) { 388 count = stAdrs; 389 edbnCnt++; 390 } 391// System.out.print( "count=[" + count + "]:" ); 392 return ( count < edAdrs ); 393 } 394 395 /** 396 * 繰り返し処理で次の要素を返します。 397 * 398 * @return Object 繰り返し処理で次の要素 399 * @throws NoSuchElementException 繰り返し処理でそれ以上要素がない場合 400 */ 401 public ExcelLayoutData next() throws NoSuchElementException { 402 if( layoutDatas == null || layoutDatas.length == count ) { 403 String errMsg = "行番号がレイアウトデータをオーバーしました。" + 404 " 行番号=[" + count + "]" ; 405 throw new NoSuchElementException( errMsg ); 406 } 407 408 ExcelLayoutData data = layoutDatas[count++]; 409 410 if( edbnCnt > 0 ) { // 繰返必須項目機能が働いているケース 411 int rowNo = data.getRowNo() + edbnCnt * incSize ; 412 data = data.copy( rowNo,edbnCnt ); 413// System.out.println( "row,edbn=[" + rowNo + "," + edbnCnt + "]:" + data ); 414 } 415 416 return data; 417 } 418 419 /** 420 * このメソッドは、このクラスからは使用できません。 421 * ※ このクラスでは実装されていません。 422 * このメソッドでは、必ず、UnsupportedOperationException が、throw されます。 423 */ 424 public void remove() { 425 String errMsg = "このメソッドは、このクラスからは使用できません。"; 426 throw new UnsupportedOperationException( errMsg ); 427 } 428 429 /** 430 * 繰返し処理を終了させます。 431 * 432 */ 433 public void setEnd() { 434 edAdrs = -1; 435 } 436}