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