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.view; 017 018import java.io.Serializable; 019import java.util.Arrays; 020import java.util.Comparator; 021import java.util.LinkedHashSet; 022import java.util.Set; 023import java.util.TreeSet; 024 025import org.opengion.fukurou.util.StringUtil; 026import org.opengion.hayabusa.common.HybsSystemException; 027import org.opengion.hayabusa.db.DBColumn; 028import org.opengion.hayabusa.db.DBColumnConfig; 029import org.opengion.hayabusa.db.DBTableModel; 030import org.opengion.hayabusa.db.DBTableModelSorter; 031import org.opengion.hayabusa.db.DBTableModelUtil; 032import org.opengion.hayabusa.db.Selection; 033import org.opengion.hayabusa.db.SelectionFactory; // 6.0.4.0 (2014/11/28) 034import org.opengion.hayabusa.html.CrossMap; 035import org.opengion.hayabusa.html.ViewCrossTableParam; 036import org.opengion.hayabusa.resource.ResourceManager; 037 038/** 039 * クロス集計テーブル作成クラスです。 040 * 041 * select dept.dname,emp.deptno,substrb(job,1,2) as X,job,mgr,sum(sal),count(*) 042 * from emp,dept 043 * where emp.deptno = dept.deptno 044 * group by dept.dname,emp.deptno,cube(job,mgr) 045 * order by emp.deptno,job,mgr; 046 * 047 * HEAD1 :ヘッダー。前段と同じデータは表示させない。 048 * HEAD2 :キーブレイクさせるカラム。また、前段と同じデータは表示させない。 049 * HEAD3 :キーブレイクさせないカラム。また、前段と同じデータでも表示させる。 050 * ROW :行データのヘッダーになるカラム 051 * COL :列データのヘッダーになるカラム。下記のSUM1,SUM2の両方のヘッダーになる。 052 * SUM1 :列データの値になるカラム。 053 * SUM2 :列データの値になるカラム。 054 * 055 * SUMカラムの数、キーブレイクのカラム名、グループ化するカラム名を 056 * 指定することで、これらのクロス集計結果の表示方法を指定します。 057 * 058 * breakColumn = "DEPTNO" キーブレイクのカラム名 059 * noGroupColumns = "X" グループ化するカラム名 060 * sumNumber = "2" SUMカラムの数 061 * cubeXColumn = "JOB" CUBE計算の1つ目(X)カラムを指定 062 * cubeYColumn = "MGR" CUBE計算の2つ目(Y)カラムを指定 063 * cubeSortType = "NUMBER" CUBE Y の列ヘッダーのソート方法を指定 064 * gokeiSortDir = "false" 合計カラムのソート方向を指定(初期値:ソートしない) 065 * shokeiLabel = "SHOKEI" 列小計のカラムに表示するラベルID 066 * gokeiLabel = "GOKEI" 列合計のカラムに表示するラベルID 067 * useHeaderColumn= "false" ヘッダーカラムにレンデラー、エディターを適用するかを指定 068 * useClassAdd = "false" 各列情報のclass属性に、カラム名などを付与するかどうかを指定 069 * useHeaderResource = "false" ヘッダー表示にラベルリソースを利用するか 070 * 071 * 各カラムの属性(HEAD,SUM等)を認識する方法 072 * 073 * HEAD1 HEAD2 HEAD3 ROW COL SUM1 SUM2 という並びを認識する方法は、 074 * 多数の前提条件を利用して、出来るだけ少ないパラメータで自動認識 075 * させています。 076 * 若干理解しにくいかもしれませんが、慣れてください。 077 * 078 * 前提条件: 079 * ROW,COL は、必ず1個ずつ存在する。 080 * HEAD群、ROW,COL,SUM群 という並びになっている。 081 * SUM群の数は、パラメータで指定する。 082 * 計算方法: 083 * HEAD数=カラム数(7)-SUM数(2)-1(ROW,COL分) = 4 個 (0 ~ 3) 084 * ROWアドレス=cubeXColumn 設定 (3) ※ アドレスは0から始まる為 085 * COLアドレス=cubeYColumn 設定 (4) 086 * SUMアドレス=HEAD数+1 ~ カラム数(7)-1 (5 ~ 6) 087 * 088 * @og.rev 3.5.4.0 (2003/11/25) 新規作成 089 * @og.group 画面表示 090 * 091 * @version 4.0 092 * @author Kazuhiko Hasegawa 093 * @since JDK5.0, 094 */ 095public class ViewForm_HTMLCrossTable extends ViewForm_HTMLTable { 096 /** このプログラムのVERSION文字列を設定します。 {@value} */ 097 private static final String VERSION = "6.8.1.1 (2017/07/22)" ; 098 099 private static final Comparator<String> NUMBER_SORT = new NumberComparator(); // 6.4.1.1 (2016/01/16) numberSort → NUMBER_SORT refactoring 100 101 private String[] groupByData ; 102 private String[] groupByCls ; 103 104 // 3.5.4.8 (2004/02/23) 機能改善 105 private int rowClmNo = -1; // ROWカラムのカラム番号 106 private int colClmNo = -1; // CLMカラムのカラム番号 107 private int headCount ; // HEADカラムの数 108 private int sumCount = 1; // 合計カラムの数 109 private int breakClmNo = -1; // ブレークするカラムのカラム番号 110 private boolean[] noGroupClm ; // グループ化する/しないのフラグ配列 111 private String shokeiLabel = "小計"; // 列小計のカラムに表示するラベルID 112 private String gokeiLabel = "合計"; // 列合計のカラムに表示するラベルID 113 private String gokeiSortDir; // 列合計のカラムをソートする方向 114 115 // 3.5.6.3 (2004/07/12) ソート方式[STRING,NUMBER,LOAD] 116 private String cubeSortType = "LOAD"; 117 118 private DBTableModel table2 ; 119 private boolean firstStep = true; 120 121 private String[] clmKeys ; // 集計部のカラムキー(集計カラムが複数でも一つ)の配列 122 private String[] clsAdd ; // 5.2.2.0 (2010/11/01) class属性に付与されるカラムキー 123 124 private String noDisplayKeys ; // 3.7.0.4 (2005/03/18) 125 private String columnDisplayKeys ; // 5.2.2.0 (2010/11/01) 126 127 private boolean firstClmGokei ; // 5.0.0.3 (2009/09/22) 128 private boolean useHeaderColumn ; // 5.2.2.0 (2010/11/01) 129 private boolean useClassAdd ; // 5.2.2.0 (2010/11/01) class属性にカラムキーを追加するかどうか 130 private boolean useHeaderResource ; // 5.5.5.0 (2012/07/28) 131 private String headerCode ; // 5.5.5.0 (2012/07/28) 132 133 /** 134 * デフォルトコンストラクター 135 * 136 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 137 */ 138 public ViewForm_HTMLCrossTable() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 139 140 /** 141 * 初期化します。 142 * ここでは、内部で使用されているキャッシュをクリアし、 143 * 新しいモデル(DBTableModel)と言語(lang) を元に内部データを再構築します。 144 * ただし、設定情報は、以前の状態がそのままキープされています。 145 * 146 * @og.rev 3.5.4.8 (2004/02/23) paramInit メソッドで、初期化を行います。 147 * @og.rev 3.5.6.1 (2004/06/25) lang 言語コード 属性を削除します。 148 * @og.rev 6.0.2.2 (2014/10/03) 初期化漏れでエラーになっていたので、修正します。 149 * 150 * @param table DBTableModelオブジェクト 151 */ 152 @Override 153 public void init( final DBTableModel table ) { 154 table2 = table; 155 firstStep = true; 156 super.init( table ); // 6.0.2.2 (2014/10/03) 初期化漏れでエラーになっていたので、修正します。 157 } 158 159 /** 160 * 内容をクリア(初期化)します。 161 * 162 * @og.rev 3.5.6.3 (2004/07/12) cubeSortType , gokeiSortDir 属性を追加します。 163 * @og.rev 3.7.0.4 (2005/03/18) noDisplayKeys 属性を追加します。 164 * @og.rev 3.7.1.1 (2005/05/31) shokeiLabel,gokeiLabel の初期値変更 165 * @og.rev 5.2.2.0 (2010/11/01) columnDisplayKeys、clsAdd、useClassAdd 属性を追加します 166 * @og.rev 5.5.5.0 (2012/07/20) useHeaderResource追加 167 */ 168 @Override 169 public void clear() { 170 super.clear(); 171 groupByData = null; 172 groupByCls = null; 173 rowClmNo = -1; // ROWカラムのカラム番号 174 colClmNo = -1; // CLMカラムのカラム番号 175 headCount = 0; // HEADカラムの数 176 sumCount = 1; // 合計カラムの数 177 breakClmNo = -1; // ブレークするカラムのカラム番号 178 noGroupClm = null; // グループ化する/しないのフラグ配列 179 table2 = null; 180 firstStep = true; 181 clmKeys = null; 182 clsAdd = null; // 5.2.2.0 (2010/11/01) 183 shokeiLabel = "小計"; // 列小計のカラムに表示するラベルID 184 gokeiLabel = "合計"; // 列合計のカラムに表示するラベルID 185 cubeSortType = "LOAD"; // 3.5.6.3 (2004/07/12) 186 gokeiSortDir = null; // 3.5.6.3 (2004/07/12) 列合計のカラムをソートする方向 187 noDisplayKeys = null; // 3.7.0.4 (2005/03/18) 188 columnDisplayKeys = null; // 5.2.2.0 (2010/11/01) 189 firstClmGokei = false; // 5.2.2.0 (2010/11/01) 190 useHeaderColumn = false; // 5.2.2.0 (2010/11/01) 191 useClassAdd = false; // 5.2.2.0 (2010/11/01) 192 useHeaderResource = false; // 5.5.5.0 (2012/07/20) 193 headerCode = null; // 5.5.5.0 (2012/07/28) 194 } 195 196 /** 197 * DBTableModel から HTML文字列を作成して返します。 198 * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。 199 * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。 200 * 201 * @og.rev 3.5.5.0 (2004/03/12) No 欄そのものの作成判断ロジックを追加 202 * @og.rev 3.5.6.1 (2004/06/25) lang 言語コード 属性を削除します。 203 * @og.rev 3.5.6.4 (2004/07/16) ヘッダーとボディー部をJavaScriptで分離 204 * @og.rev 3.7.0.4 (2005/03/18) setNoDisplay メソッドを追加 205 * @og.rev 4.3.1.0 (2008/09/08) 編集行のみを表示する属性(isSkipNoEdit)追加 206 * @og.rev 5.0.0.3 (2009/09/22) 合計列をcubeの先頭に出せるようにする 207 * @og.rev 5.1.0.0 (2009/11/04) ↑で合計列が複数カラム存在する場合に正しく表示されないバグを修正 208 * @og.rev 5.2.2.0 (2010/11/01) setColumnDisplay メソッドを追加 209 * @og.rev 6.8.1.1 (2017/07/22) ckboxTD変数は、<td> から <td に変更します(タグの最後が記述されていない状態でもらう)。 210 * 211 * @param startNo 表示開始位置 212 * @param pageSize 表示件数 213 * 214 * @return DBTableModelから作成された HTML文字列 215 * @og.rtnNotNull 216 */ 217 @Override 218 public String create( final int startNo, final int pageSize ) { 219 if( firstStep ) { 220 paramInit( table2 ); 221 super.init( makeCrossTable(table2) ); 222 super.setNoDisplay( noDisplayKeys ) ; // 3.7.0.4 (2005/03/18) 223 super.setColumnDisplay( columnDisplayKeys ) ; // 5.2.2.0 (2010/11/01) 224 markerSet( this ); // 3.5.6.4 (2004/07/16) 225 firstStep = false; 226 } 227 228 if( getRowCount() == 0 ) { return ""; } // 暫定処置 229 230 final int clmCnt = getColumnCount(); // 3.5.5.7 (2004/05/10) 231 232 headerLine = null; 233 234 final int lastNo = getLastNo( startNo, pageSize ); 235 final int blc = getBackLinkCount(); 236 String backData = null; 237 238 final StringBuilder out = new StringBuilder( BUFFER_LARGE ) 239 .append( getCountForm( startNo,pageSize ) ) 240 .append( getHeader() ); 241 242 final String ckboxTD = " <td class=\"" + ViewCrossTableParam.HEADER1 + "\""; // 6.8.1.1 (2017/07/22) 243 244 out.append("<tbody>").append( CR ); 245 int bgClrCnt = 0; 246 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 247 for( int row=startNo; row<lastNo; row++ ) { 248 if( isSkip( row ) || isSkipNoEdit( row ) ) { continue; } // 4.3.1.0 (2008/09/08) 249 // キーブレイク時のヘッダー設定 250 if( breakClmNo >= 0 ) { 251 final String val = getValue( row,breakClmNo ); 252 if( backData == null ) { // キーブレイクの初期データ設定。 253 backData = val; 254 } 255 else { 256 if( ! backData.equals( val ) ) { 257 backData = val; 258 out.append( getHeadLine() ); 259 } 260 } 261 } 262 // 小計ヘッダー時のクラス設定 263 final boolean shokei = getValue( row,rowClmNo ).isEmpty(); // 6.3.9.1 (2015/11/27) 264 if( shokei ) { // 6.3.9.1 (2015/11/27) 265 out.append(" <tr class=\"").append( ViewCrossTableParam.SHOKEI ).append("\">"); 266 } 267 else { 268 out.append(" <tr").append( getBgColorCycleClass( bgClrCnt++ ) ).append('>'); // 6.0.2.5 (2014/10/31) char を append する。 269 } 270 out.append( CR ); 271 // 3.5.5.0 (2004/03/12) No 欄そのものの作成判断追加 272 if( isNumberDisplay() ) { 273 out.append( makeCheckbox( ckboxTD, row, blc ) ).append( CR ); 274 } 275 for( int column=0; column<clmCnt; column++ ) { 276 if( isColumnDisplay( column ) ) { 277 if( column < headCount-1 ) { // CUBEではない行ヘッダー部 278 final String val = getGroupData( column,getRendererValue(row,column) ); 279 out.append(" <td class=\"").append( groupByCls[column] ).append("\">") 280 .append( val ); 281 } 282 else if( column == headCount-1 ) { // ヘッダーの最後尾 283 if( shokei ) { 284 out.append(" <td class=\"").append( ViewCrossTableParam.SHOKEI ).append("\">") 285 .append( shokeiLabel ); 286 } 287 else { 288 if( breakClmNo > 0 ) { // ヘッダーがある場合 289 out.append(" <td class=\"").append( groupByCls[column-1] ).append("\">"); 290 } 291 else { 292 out.append(" <td class=\"").append( ViewCrossTableParam.HEADER1 ).append("\">"); 293 } 294 out.append( getRendererValue(row,column) ); 295 } 296 } 297 // else if( column >= clmCnt-sumCount ) { // CUBEの最終カラム(列合計) 298 else if( column >= clmCnt-sumCount && ! firstClmGokei ) { // 5.0.0.3 (2009/09/22) CUBEの最終カラム(列合計) 299 out.append(" <td class=\"").append( ViewCrossTableParam.SHOKEI ).append("\">") 300 .append( getRendererValue(row,column) ); 301 } 302 else if( column >= headCount && column < headCount + sumCount && firstClmGokei ) { // 5.1.0.0 (2009/11/04) 303 out.append(" <td class=\"").append( ViewCrossTableParam.SHOKEI ).append("\">") 304 .append( getRendererValue(row,clmCnt-sumCount+(column-headCount)) ); // 5.1.0.0 (2009/11/04) 305 } 306 else { // カラム SUM列 307 if( useClassAdd && clsAdd[column] != null ) { 308 out.append(" <td class=\"").append( clsAdd[column] ).append("\">"); 309 } 310 else { 311 out.append(" <td>"); 312 } 313 if( firstClmGokei ){ 314 out.append( getRendererValue(row,column-sumCount) ); // 5.1.0.0 (2009/11/04) 315 } 316 else{ 317 out.append( getRendererValue(row,column) ); 318 } 319 } 320 out.append(" </td>").append( CR ); 321 } 322 } 323 out.append(" </tr>").append( CR ); 324 } 325 out.append("</tbody>").append( CR ) 326 .append("</table>").append( CR ) 327 .append( getScrollBarEndDiv() ); // 3.8.0.3 (2005/07/15) 328 329 return out.toString(); 330 } 331 332 /** 333 * パラメータ内容を初期化します。 334 * 335 * @og.rev 3.5.4.8 (2004/02/23) 新規作成 336 * @og.rev 3.5.6.3 (2004/07/12) 列ヘッダーのソート方法を指定 337 * @og.rev 5.0.0.3 (2009/09/22) 合計行をCUBEの先頭に持ってくるためのフラグ追加 338 * @og.rev 5.2.2.0 (2010/11/01) useHeaderColumn,useClassAdd 属性の追加 339 * 340 * @param table 入力もとの DBTableModelオブジェクト 341 */ 342 private void paramInit( final DBTableModel table ) { 343 final String breakColumn = getParam( ViewCrossTableParam.BREAK_COLUMN_KEY , null ); 344 final String noGroupColumns = getParam( ViewCrossTableParam.NO_GROUP_COLUMNS_KEY , null ); 345 final String sumNumber = getParam( ViewCrossTableParam.SUM_NUMBER_KEY , null ); 346 shokeiLabel = getParam( ViewCrossTableParam.SHOKEI_LABEL_KEY , shokeiLabel ); 347 gokeiLabel = getParam( ViewCrossTableParam.GOKEI_LABEL_KEY , gokeiLabel ); 348 final String cubeXColumn = getParam( ViewCrossTableParam.CUBE_X_COLUMN_KEY , null ); // CUBE計算の1つ目(X)カラムを指定 349 final String cubeYColumn = getParam( ViewCrossTableParam.CUBE_Y_COLUMN_KEY , null ); // CUBE計算の2つ目(Y)カラムを指定 350 cubeSortType = getParam( ViewCrossTableParam.CUBE_SORT_TYPE_KEY , "LOAD" ); // 3.5.6.3 (2004/07/12) 351 gokeiSortDir = getParam( ViewCrossTableParam.GOKEI_SORT_DIR_KEY , null ); // 3.5.6.3 (2004/07/12) 352 firstClmGokei = StringUtil.nval( getParam( ViewCrossTableParam.FIRST_CLM_GOKEI_KEY , null ), false); // 5.0.0.3 (2009/09/22) 353 useHeaderColumn = StringUtil.nval( getParam( ViewCrossTableParam.USE_HEADER_COLUMN , null ), false); // 5.2.2.0 (2010/11/01) 354 useClassAdd = StringUtil.nval( getParam( ViewCrossTableParam.USE_CLASS_ADD , null ), false); // 5.2.2.0 (2010/11/01) 355 useHeaderResource = StringUtil.nval( getParam( ViewCrossTableParam.USE_HEADER_RSC , null ), false); // 5.5.5.0 (2012/07/20) 356 headerCode = getParam( ViewCrossTableParam.HEADER_CODE_KEY , null ); // 5.5.5.0 (2012/07/28) 357 358 if( sumNumber != null ) { 359 sumCount = Integer.parseInt( sumNumber ); 360 } 361 362 // HEAD数=カラム数-SUM数-1(COL分) ROW は、HEADに含みます。 363 headCount = table.getColumnCount() - sumCount - 1; 364 365 // 3.5.5.9 (2004/06/07) 366 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 367 rowClmNo = cubeXColumn == null 368 ? headCount-1 // ROWカラムのカラム番号 369 : table.getColumnNo( cubeXColumn ); 370 371 // 3.5.5.9 (2004/06/07) 372 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 373 colClmNo = cubeYColumn == null 374 ? headCount // CLMカラムのカラム番号 375 : table.getColumnNo( cubeYColumn ); 376 377 if( breakColumn != null ) { 378 breakClmNo = table.getColumnNo( breakColumn ); 379 } 380 381 groupByData = new String[headCount]; 382 groupByCls = new String[headCount]; 383 Arrays.fill( groupByCls,ViewCrossTableParam.HEADER2 ); // 変であるが、最初に入れ替えが発生する為。 384 385 noGroupClm = new boolean[headCount]; // グループ化する/しないのフラグ配列 386 Arrays.fill( noGroupClm,false ); 387 388 if( noGroupColumns != null ) { 389 final String[] gClms = StringUtil.csv2Array( noGroupColumns ); 390 for( int i=0; i<gClms.length; i++ ) { 391 noGroupClm[table.getColumnNo( gClms[i] )] = true; 392 } 393 } 394 395 if( ! "true".equalsIgnoreCase( gokeiSortDir ) && 396 ! "false".equalsIgnoreCase( gokeiSortDir ) ) { 397 gokeiSortDir = null; 398 } 399 } 400 401 /** 402 * CUBEではない行ヘッダー部の値が前と同じならば、ゼロ文字列を返します。 403 * 404 * @param clm カラム番号 405 * @param val 比較する値 406 * 407 * @return 前と同じなら,""を、異なる場合は、引数の val を返します。 408 */ 409 private String getGroupData( final int clm,final String val ) { 410 // 6.3.9.1 (2015/11/27) A method should have only one exit point, and that should be the last statement in the method.(PMD) 411 final String rtn ; 412 if( noGroupClm[clm] ) { rtn = val; } 413 else if( val.equals( groupByData[clm] )) { 414 rtn = ""; 415 } 416 else { 417 rtn = val; 418 groupByData[clm] = val; 419 groupByCls[clm] = groupByCls[clm].equals( ViewCrossTableParam.HEADER1 ) 420 ? ViewCrossTableParam.HEADER2 421 : ViewCrossTableParam.HEADER1 ; 422 } 423 return rtn ; 424 } 425 426 /** 427 * 選択用のチェックボックスと行番号と変更タイプ(A,C,D)を表示します。 428 * 429 * @og.rev 6.8.1.1 (2017/07/22) ckboxTD変数は、<td> から <td に変更します(タグの最後が記述されていない状態でもらう)。 430 * 431 * @param ckboxTD チェックボックスのタグ(マルチカラム時のrowspan対応) 432 * @param row 行番号 433 * @param blc バックラインカウント(先頭へ戻るリンク間隔) 434 * 435 * @return tdタグで囲まれたチェックボックスのHTML文字列 436 * @og.rtnNotNull 437 */ 438 @Override 439 protected String makeCheckbox( final String ckboxTD,final int row,final int blc ) { 440 final StringBuilder out = new StringBuilder( BUFFER_MIDDLE ) 441 .append( ckboxTD ).append( "></td>" ) 442 .append( ckboxTD ).append( "></td>" ) 443 .append( ckboxTD ).append( '>' ); // 6.8.1.1 (2017/07/22) 444 // 3.5.1.0 (2003/10/03) Noカラムに、numberType 属性を追加 445 if( blc != 0 && (row+1) % blc == 0 ) { 446 out.append( "<a href=\"#top\">" ).append( row+1 ).append( "</a>" ); 447 } else { 448 out.append( row+1 ); 449 } 450 out.append("</td>"); 451 452 return out.toString(); 453 } 454 455 /** 456 * ヘッダー繰り返し部を、getTableHead()メソッドから分離。 457 * 458 * @og.rev 3.5.4.5 (2004/01/23) 実装をgetHeadLine( String thTag )に移動 459 * @og.rev 3.5.5.0 (2004/03/12) No 欄そのものの作成判断ロジックを追加 460 * @og.rev 5.0.0.3 (2009/09/17) 合計行を出力する位置をfirstClmGokeiで変える 461 * @og.rev 5.2.2.0 (2010/11/01) 集計部の ColumnDisplay/NoDisplay 対応 462 * @og.rev 5.5.5.0 (2012/07/28) useHeaderResource利用時のヘッダのラベル/コードリソース対応 463 * @og.rev 5.7.4.2 (2014/03/20) ヘッダーのリソース適用見直し 464 * @og.rev 5.7.4.3 (2014/03/28) useHeaderResource 単独でリソース適用します。 465 * @og.rev 6.0.4.0 (2014/11/28) selection は、Column から取得するのではなく、Factory で作成する。 466 * 467 * @return テーブルのタグ文字列 468 * @og.rtnNotNull 469 */ 470 @Override 471 protected String getHeadLine() { 472 if( headerLine != null ) { return headerLine; } // キャッシュを返す。 473 474 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 475 final String rowspan = sumCount > 1 ? " rowspan=\"2\"" : ""; 476 final String thTag = "<th" + rowspan; 477 478 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 479 .append("<tr").append( rowspan ).append(" class=\"row_h\" >").append( CR ); 480 481 // 3.5.5.0 (2004/03/12) No 欄そのものの作成判断追加 482 if( isNumberDisplay() ) { 483 buf.append( thTag ).append(" colspan='3'>").append( getNumberHeader() ).append("</th>"); 484 } 485 486 buf.append( CR ); 487 // ヘッダー部分は、そのまま表示します。 488 for( int column=0; column<headCount; column++ ) { 489 if( isColumnDisplay( column ) ) { 490 buf.append( thTag ).append('>') // 6.0.2.5 (2014/10/31) char を append する。 491 .append( getColumnLabel(column) ) 492 .append("</th>").append( CR ); 493 } 494 } 495 496 // ヘッダー部分(上段)は、カラム配列を利用します。 497 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 498 final String colspan = sumCount > 1 ? " colspan='" + sumCount + "'" : ""; 499 500 // 5.2.2.0 (2010/11/01) 集計部の ColumnDisplay/NoDisplay 対応 501 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 502 final String gokeiClm ; 503 if( isColumnDisplay( headCount+(clmKeys.length-1)*sumCount ) ) { 504 String temp = clmKeys[clmKeys.length-1]; 505 if( temp == null || temp.isEmpty() ) { // 6.1.0.0 (2014/12/26) refactoring 506 temp = gokeiLabel; 507 } 508 509 gokeiClm = "<th" + colspan + ">" + temp + "</th>" + CR ; 510 } 511 else { 512 gokeiClm = null ; // 6.3.9.1 (2015/11/27) 513 } 514 515 // 5.2.2.0 (2010/11/01) 最後のカラムが、合計行。 516 // 5.0.0.3 (2009/09/22) firstClmGokei が trueの場合はcubeの先頭に出すようにします。 517 if( firstClmGokei && gokeiClm != null ) { 518 buf.append( gokeiClm ); 519 } 520 521 // 3.7.0.4 (2005/03/18) カラム配列は、カラム番号と別物 522 final ResourceManager resource = getResourceManager(); 523 Selection selection = null; 524 if( headerCode != null && headerCode.length() > 0 && resource != null ){ 525 final DBColumn clmTmp = resource.getDBColumn( headerCode ); 526 if( clmTmp != null ){ 527 // 6.0.4.0 (2014/11/28) selection は、Column から取得するのではなく、Factory で作成する。 528 selection = SelectionFactory.newSelection( "MENU",clmTmp.getCodeData(),null ); // 6.2.0.0 (2015/02/27) キー:ラベル形式 529 } 530 } 531 532 // 5.7.4.2 (2014/03/20) ヘッダーのリソース適用見直し 533 // 5.7.4.3 (2014/03/28) useHeaderResource 単独でリソース適用します。 534 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 535 final DBColumn colClm = useHeaderResource ? table2.getDBColumn( colClmNo ) : null ; 536 for( int keyNo=0; keyNo<clmKeys.length-1; keyNo++ ) { 537 // 5.2.2.0 (2010/11/01) ColumnDisplay/NoDisplay 対応 538 if( isColumnDisplay( headCount+keyNo ) ) { 539 buf.append( "<th").append( colspan ).append( '>' ); // 6.0.2.5 (2014/10/31) char を append する。 540 if( selection != null ){ 541 buf.append( selection.getValueLabel( clmKeys[keyNo] ) ); 542 } 543 // 5.7.4.3 (2014/03/28) ヘッダーのリソース適用は、CLMカラムのカラム番号のみとします。 544 else if( colClm != null ) { 545 buf.append( colClm.getRendererValue( clmKeys[keyNo] ) ); 546 } 547 else{ 548 buf.append( clmKeys[keyNo] ); 549 } 550 buf.append("</th>").append( CR ); 551 } 552 } 553 554 // 5.2.2.0 (2010/11/01) 最後のカラムが、合計行。 555 // 5.0.0.3 (2009/09/22) firstClmGokei が trueの場合はcubeの先頭に出すようにします。 556 if( ! firstClmGokei && gokeiClm != null ) { 557 buf.append( gokeiClm ); 558 } 559 560 buf.append("</tr>").append( CR ); 561 562 if( sumCount > 1 ) { 563 buf.append("<tr class=\"row_h\" >").append( CR ); 564 final int clmCnt = getColumnCount(); // 3.5.5.7 (2004/05/10) 565 for( int column=headCount; column<clmCnt; column++ ) { 566 if( isColumnDisplay( column ) ) { 567 buf.append( "<th>").append( getColumnLabel(column) ).append("</th>").append( CR ); 568 } 569 } 570 buf.append("</tr>").append( CR ); 571 } 572 573 headerLine = buf.toString(); 574 return headerLine; 575 } 576 577 /** 578 * クロス集計結果の DBTableModelオブジェクトを作成します。 579 * 580 * @og.rev 3.5.4.8 (2004/02/23) paramInit メソッドで、初期化を行います。 581 * @og.rev 3.5.6.3 (2004/07/12) 列ヘッダーのソート可否の指定を追加 582 * @og.rev 4.0.0.0 (2007/11/27) ヘッダーカラムのエディター、レンデラー適用対応 583 * @og.rev 4.3.5.7 (2008/03/22) ↑リソースが存在しない場合は、ラベルのみ入れ替え 584 * @og.rev 5.2.2.0 (2010/11/01) useHeaderColumn,useClassAdd 属性の追加 585 * @og.rev 5.7.4.3 (2014/03/28) useHeaderColumn の適用条件を、最初の集計カラムのみに変更。 586 * @og.rev 6.4.3.2 (2016/02/19) clmKey が null の場合の処理を追加。 587 * 588 * @param table 入力もとの DBTableModelオブジェクト 589 * 590 * @return DBTableModelオブジェクト 591 * @og.rtnNotNull 592 */ 593 private DBTableModel makeCrossTable( final DBTableModel table ) { 594 final Set<String> clmData = gatSortAlgorithmSet(); 595 596 // 列のキーとなるカラムの値を取得します。 597 final int rowCnt = table.getRowCount(); // 3.5.5.7 (2004/05/10) 598 for( int row=0; row<rowCnt; row++ ) { 599 final String clm = table.getValue( row,colClmNo ); 600 if( clm.length() > 0 ) { clmData.add( clm ); } 601 } 602 // ゼロストリングは、合計行になりますので、最後に追加します。 603 604 // 3.5.6.3 (2004/07/12) ゼロストリングは、合計行になりますので、最後に追加します。 605 clmKeys = clmData.toArray( new String[clmData.size() + 1] ) ; 606 607 clmKeys[clmKeys.length-1] = "" ; 608 609 final int numberOfColumns = headCount + clmKeys.length * sumCount ; 610 611 final DBTableModel tableImpl = DBTableModelUtil.newDBTable(); 612 tableImpl.init( numberOfColumns ); 613 614 // ヘッダーカラム(ROWデータ含む)は、そのまま、設定します。 615 for( int column=0; column<headCount; column++ ) { 616 tableImpl.setDBColumn( column,table.getDBColumn(column) ); 617 } 618 619 // 列情報は、合計値のカラム定義を使用します。 620 DBColumn[] dbColumn = new DBColumn[sumCount]; 621 for( int i=0; i<sumCount; i++ ) { 622 dbColumn[i] = table.getDBColumn(headCount + 1 + i); 623 } 624 625 // 列情報は、列の名前をカラムの値に変えて、合計カラム列のコピー情報を設定します。 626 627 int sumId = 0; 628 final ResourceManager resource = getResourceManager(); 629 useHeaderColumn = useHeaderColumn && resource != null ; // 5.2.2.0 (2010/11/01) 630 631 // 5.2.2.0 (2010/11/01) useClassAdd 属性の追加 632 633 clsAdd = new String[numberOfColumns]; 634 635 // 列情報カラムは、ヘッダー分に割り当てられる為、開始が、headCount からになります。 636 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); // 6.1.0.0 (2014/12/26) refactoring 637 for( int column=headCount; column<numberOfColumns; column++ ) { 638 DBColumn dbClm = dbColumn[sumId]; 639 final String clmKey = clmKeys[ (column-headCount)/sumCount ]; 640 641 // 5.2.2.0 (2010/11/01) useClassAdd 属性の追加 642 if( useClassAdd ) { 643 // ※ 特殊対応:cssなどで指定できるIDやCLASS属性は、先頭文字が数字の場合は、 644 // 無効になります。(つまり、効きません。) 645 // 表示ヘッダーは、年月や、社員番号(数字)などのケースもあります。そこで、先頭が数字の 646 // 場合は、"x"(小文字のx)を自動的に頭に追加します。 647 buf.setLength(0); 648 if( clmKey != null && clmKey.length() > 0 ) { 649 final char ch = clmKey.charAt(0); 650 if( ch >= '0' && ch <= '9' ) { 651 buf.append( 'x' ); // 6.0.2.5 (2014/10/31) char を append する。 652 } 653 buf.append( clmKey ); 654 } 655 656 final String nm = dbClm.getName(); 657 if( nm != null && nm.length() > 0 ) { 658 buf.append( ' ' ); // 6.0.2.5 (2014/10/31) char を append する。 659 final char ch = nm.charAt(0); 660 if( ch >= '0' && ch <= '9' ) { 661 buf.append( 'x' ); // 6.0.2.5 (2014/10/31) char を append する。 662 } 663 buf.append( nm ); 664 } 665 clsAdd[column] = buf.toString(); 666 } 667 668 // 5.7.4.3 (2014/03/28) useHeaderColumn の適用条件を、最初の集計カラムのみに変更。 669 if( useHeaderColumn && sumId == 0 ) { 670 // 6.4.3.2 (2016/02/19) clmKey が null の場合の処理を追加。 671 if( clmKey == null || clmKey.isEmpty() ) { 672 final DBColumnConfig dbCfg2 = dbClm.getConfig(); 673 dbCfg2.setLabelData( resource.getLabelData( gokeiLabel ) ); 674 dbClm = new DBColumn( dbCfg2 ); 675 } 676 else { 677 final DBColumn clmTmp = resource.getDBColumn( clmKey ); 678 if( clmTmp == null ) { 679 final DBColumnConfig dbCfg2 = dbClm.getConfig(); 680 dbCfg2.setName( clmKey ); 681 dbCfg2.setLabelData( resource.getLabelData( clmKey ) ); 682 dbClm = new DBColumn( dbCfg2 ); 683 } 684 else { 685 dbClm = clmTmp; 686 } 687 } 688 } 689 690 tableImpl.setDBColumn( column,dbClm ); 691 692 sumId++; 693 if( sumId % sumCount == 0 ) { 694 sumId = 0; 695 } 696 } 697 698 // クロス集計データの作成 699 final CrossMap cross = new CrossMap( clmKeys,headCount,sumCount ); 700 for( int row=0; row<rowCnt; row++ ) { 701 final String[] data = table.getValues( row ); 702 cross.add( data ); 703 } 704 705 // データ部の設定 706 final int size = cross.getSize(); 707 for( int row=0; row<size; row++ ) { 708 tableImpl.addValues( cross.get( row ), row ); 709 } 710 711 tableImpl.resetModify(); 712 713 final DBTableModel model ; 714 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 715 if( gokeiSortDir == null ) { 716 model = tableImpl; 717 } 718 else { 719 final DBTableModelSorter temp = new DBTableModelSorter(); 720 temp.setModel( tableImpl ); 721 722 final boolean direction = Boolean.parseBoolean( gokeiSortDir ); // 6.1.0.0 (2014/12/26) refactoring 723 temp.sortByColumn( numberOfColumns-1,direction ); 724 model = temp ; 725 } 726 727 return model ; 728 } 729 730 /** 731 * 列ヘッダーのソート方法に応じた、Setオブジェクトを返します。 732 * ここでは、NUMBER , STRING , LOAD の3種類用意しています。 733 * 734 * @og.rev 3.5.6.3 (2004/07/12) 新規作成 735 * 736 * @return ソート方法に応じたSetオブジェクト 737 * @og.rtnNotNull 738 */ 739 private Set<String> gatSortAlgorithmSet() { 740 final Set<String> rtnSet ; 741 742 if( "LOAD".equalsIgnoreCase( cubeSortType ) ) { 743 rtnSet = new LinkedHashSet<>(); 744 } 745 else if( "NUMBER".equalsIgnoreCase( cubeSortType ) ) { 746 rtnSet = new TreeSet<>( NUMBER_SORT ); 747 } 748 else if( "STRING".equalsIgnoreCase( cubeSortType ) ) { 749 rtnSet = new TreeSet<>(); 750 } 751 else { 752 final String errMsg = "cubeSortType は、NUMBER,STRING,LOAD 以外指定できません。" + 753 " cubeSortType=[" + cubeSortType + "]"; 754 throw new HybsSystemException( errMsg ); 755 } 756 757 return rtnSet ; 758 } 759 760 /** 761 * 表示不可カラム名を、CSV形式で与えます。 762 * 例:"OYA,KO,HJO,SU,DYSET,DYUPD" 763 * null を与えた場合は,なにもしません。 764 * 765 * 注意:このクラスでは、DBTableModel を作り直すタイミングが、 766 * create メソッド実行時です。(パラメータの初期化が必要な為) 767 * よって、このメソッドは、初期が終了後に、再セットします。 768 * 769 * @og.rev 3.7.0.4 (2005/03/18) 新規作成 770 * 771 * @param columnName カラム名 772 */ 773 @Override 774 public void setNoDisplay( final String columnName ) { 775 noDisplayKeys = columnName; 776 } 777 778 /** 779 * 表示可能カラム名を、CSV形式で与えます。 780 * 例:"OYA,KO,HJO,SU,DYSET,DYUPD" 781 * setColumnDisplay( int column,boolean rw ) の簡易版です。 782 * null を与えた場合は,なにもしません。 783 * また、全カラムについて、有効にする場合は、columnName="*" を設定します。 784 * 785 * @og.rev 5.2.2.0 (2010/11/01) 新規追加 786 * 787 * @param columnName カラム名 788 */ 789 @Override 790 public void setColumnDisplay( final String columnName ) { 791 columnDisplayKeys = columnName; 792 } 793 794 /** 795 * NUMBER ソート機能(整数限定) 内部クラス 796 * これは通常のソートではなく、ヘッダーに使うラベルのソートなので、 797 * 整数のみと限定します。実数の場合は、桁合わせ(小数点以下の桁数) 798 * されているという前提です。 799 * 800 * @og.rev 3.5.6.3 (2004/07/12) 新規作成 801 */ 802 private static final class NumberComparator implements Comparator<String>,Serializable { 803 private static final long serialVersionUID = 400020050131L ; // 4.0.0.0 (2005/01/31) 804 805 /** 806 * 順序付けのために2つの引数を比較します。 807 * 808 * Comparator<String> インタフェースの実装です。 809 * 810 * @param s1 比較対象の最初のString 811 * @param s2 比較対象の2番目のString 812 * @return 最初の引数が2番目の引数より小さい場合は負の整数、両方が等しい場合は0、最初の引数が2番目の引数より大きい場合は正の整数。 813 */ 814 @Override 815 public int compare( final String s1, final String s2 ) { 816 // 6.3.9.1 (2015/11/27) A method should have only one exit point, and that should be the last statement in the method.(PMD) 817 final int rtn ; 818 if( s1.length() > s2.length() ) { rtn = 1; } 819 else if( s1.length() < s2.length() ) { rtn = -1; } 820 else { 821 rtn = s1.compareTo( s2 ); 822 } 823 return rtn; 824 } 825 } 826 827 /** 828 * 表示項目の編集(並び替え)が可能かどうかを返します。 829 * 830 * @og.rev 5.1.6.0 (2010/05/01) 新規追加 831 * 832 * @return 表示項目の編集(並び替え)が可能かどうか(false:不可能) 833 */ 834 @Override 835 public boolean isEditable() { 836 return false; 837 } 838}