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.0.1.7 (2019/01/21)" ; 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 for( int i=0; i<gClms.length; i++ ) { 394 noGroupClm[table.getColumnNo( gClms[i] )] = true; 395 } 396 } 397 398 if( ! "true".equalsIgnoreCase( gokeiSortDir ) && 399 ! "false".equalsIgnoreCase( gokeiSortDir ) ) { 400 gokeiSortDir = null; 401 } 402 } 403 404 /** 405 * CUBEではない行ヘッダー部の値が前と同じならば、ゼロ文字列を返します。 406 * 407 * @param clm カラム番号 408 * @param val 比較する値 409 * 410 * @return 前と同じなら,""を、異なる場合は、引数の val を返します。 411 */ 412 private String getGroupData( final int clm,final String val ) { 413 // 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) 414 final String rtn ; 415 if( noGroupClm[clm] ) { rtn = val; } 416 else if( val.equals( groupByData[clm] )) { 417 rtn = ""; 418 } 419 else { 420 rtn = val; 421 groupByData[clm] = val; 422 groupByCls[clm] = groupByCls[clm].equals( ViewCrossTableParam.HEADER1 ) 423 ? ViewCrossTableParam.HEADER2 424 : ViewCrossTableParam.HEADER1 ; 425 } 426 return rtn ; 427 } 428 429 /** 430 * 選択用のチェックボックスと行番号と変更タイプ(A,C,D)を表示します。 431 * 432 * @og.rev 6.8.1.1 (2017/07/22) ckboxTD変数は、<td> から <td に変更します(タグの最後が記述されていない状態でもらう)。 433 * 434 * @param ckboxTD チェックボックスのタグ(マルチカラム時のrowspan対応) 435 * @param row 行番号 436 * @param blc バックラインカウント(先頭へ戻るリンク間隔) 437 * 438 * @return tdタグで囲まれたチェックボックスのHTML文字列 439 * @og.rtnNotNull 440 */ 441 @Override 442 protected String makeCheckbox( final String ckboxTD,final int row,final int blc ) { 443 final StringBuilder out = new StringBuilder( BUFFER_MIDDLE ) 444 .append( ckboxTD ).append( "></td>" ) 445 .append( ckboxTD ).append( "></td>" ) 446 .append( ckboxTD ).append( '>' ); // 6.8.1.1 (2017/07/22) 447 // 3.5.1.0 (2003/10/03) Noカラムに、numberType 属性を追加 448 if( blc != 0 && (row+1) % blc == 0 ) { 449 out.append( "<a href=\"#top\">" ).append( row+1 ).append( "</a>" ); 450 } else { 451 out.append( row+1 ); 452 } 453 out.append("</td>"); 454 455 return out.toString(); 456 } 457 458 /** 459 * ヘッダー繰り返し部を、getTableHead()メソッドから分離。 460 * 461 * @og.rev 3.5.4.5 (2004/01/23) 実装をgetHeadLine( String thTag )に移動 462 * @og.rev 3.5.5.0 (2004/03/12) No 欄そのものの作成判断ロジックを追加 463 * @og.rev 5.0.0.3 (2009/09/17) 合計行を出力する位置をfirstClmGokeiで変える 464 * @og.rev 5.2.2.0 (2010/11/01) 集計部の ColumnDisplay/NoDisplay 対応 465 * @og.rev 5.5.5.0 (2012/07/28) useHeaderResource利用時のヘッダのラベル/コードリソース対応 466 * @og.rev 5.7.4.2 (2014/03/20) ヘッダーのリソース適用見直し 467 * @og.rev 5.7.4.3 (2014/03/28) useHeaderResource 単独でリソース適用します。 468 * @og.rev 6.0.4.0 (2014/11/28) selection は、Column から取得するのではなく、Factory で作成する。 469 * @og.rev 7.0.1.5 (2018/12/10) headerCodeColumn 廃止 470 * @og.rev 7.0.1.7 (2019/01/21) ヘッダー作成時にオリジナルのカラムを使うので、インスタンス変数化します。 471 * 472 * @return テーブルのタグ文字列 473 * @og.rtnNotNull 474 */ 475 @Override 476 protected String getHeadLine() { 477 if( headerLine != null ) { return headerLine; } // キャッシュを返す。 478 479 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 480 final String rowspan = sumCount > 1 ? " rowspan=\"2\"" : ""; 481 final String thTag = "<th" + rowspan; 482 483 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 484 .append("<tr").append( rowspan ).append(" class=\"row_h\" >").append( CR ); 485 486 // 3.5.5.0 (2004/03/12) No 欄そのものの作成判断追加 487 if( isNumberDisplay() ) { 488 buf.append( thTag ).append(" colspan='3'>").append( getNumberHeader() ).append("</th>"); 489 } 490 491 buf.append( CR ); 492 // ヘッダー部分は、そのまま表示します。 493 for( int column=0; column<headCount; column++ ) { 494 if( isColumnDisplay( column ) ) { 495 buf.append( thTag ).append('>') // 6.0.2.5 (2014/10/31) char を append する。 496 .append( getColumnLabel(column) ) 497 .append("</th>").append( CR ); 498 } 499 } 500 501 // ヘッダー部分(上段)は、カラム配列を利用します。 502 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 503 final String colspan = sumCount > 1 ? " colspan='" + sumCount + "'" : ""; 504 505 // 5.2.2.0 (2010/11/01) 集計部の ColumnDisplay/NoDisplay 対応 506 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 507 final String gokeiClm ; 508 if( isColumnDisplay( headCount+(clmKeys.length-1)*sumCount ) ) { 509 String temp = clmKeys[clmKeys.length-1]; 510 if( temp == null || temp.isEmpty() ) { // 6.1.0.0 (2014/12/26) refactoring 511 temp = gokeiLabel; 512 } 513 514 gokeiClm = "<th" + colspan + ">" + temp + "</th>" + CR ; 515 } 516 else { 517 gokeiClm = null ; // 6.3.9.1 (2015/11/27) 518 } 519 520 // 5.2.2.0 (2010/11/01) 最後のカラムが、合計行。 521 // 5.0.0.3 (2009/09/22) firstClmGokei が trueの場合はcubeの先頭に出すようにします。 522 if( firstClmGokei && gokeiClm != null ) { 523 buf.append( gokeiClm ); 524 } 525 526 // 3.7.0.4 (2005/03/18) カラム配列は、カラム番号と別物 527// // 7.0.1.5 (2018/12/10) headerCodeColumn 廃止 528// final ResourceManager resource = getResourceManager(); 529// Selection selection = null; 530// if( headerCode != null && headerCode.length() > 0 && resource != null ){ 531// final DBColumn clmTmp = resource.getDBColumn( headerCode ); 532// if( clmTmp != null ){ 533// // 6.0.4.0 (2014/11/28) selection は、Column から取得するのではなく、Factory で作成する。 534// selection = SelectionFactory.newSelection( "MENU",clmTmp.getCodeData(),null ); // 6.2.0.0 (2015/02/27) キー:ラベル形式 535// } 536// } 537 538 // 5.7.4.2 (2014/03/20) ヘッダーのリソース適用見直し 539 // 5.7.4.3 (2014/03/28) useHeaderResource 単独でリソース適用します。 540 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 541 final DBColumn colClm = useHeaderResource ? table2.getDBColumn( colClmNo ) : null ; 542 543 for( int keyNo=0; keyNo<clmKeys.length-1; keyNo++ ) { 544 // 5.2.2.0 (2010/11/01) ColumnDisplay/NoDisplay 対応 545 if( isColumnDisplay( headCount+keyNo ) ) { 546 buf.append( "<th").append( colspan ).append( '>' ); // 6.0.2.5 (2014/10/31) char を append する。 547// // 7.0.1.5 (2018/12/10) headerCodeColumn 廃止 548// if( selection != null ){ 549// buf.append( selection.getValueLabel( clmKeys[keyNo] ) ); 550// } 551 // 5.7.4.3 (2014/03/28) ヘッダーのリソース適用は、CLMカラムのカラム番号のみとします。 552// else if( colClm != null ) { 553 if( colClm != null ) { 554 buf.append( colClm.getRendererValue( clmKeys[keyNo] ) ); 555 } 556 else{ 557 buf.append( clmKeys[keyNo] ); 558 } 559 buf.append("</th>").append( CR ); 560 } 561 } 562 563 // 5.2.2.0 (2010/11/01) 最後のカラムが、合計行。 564 // 5.0.0.3 (2009/09/22) firstClmGokei が trueの場合はcubeの先頭に出すようにします。 565 if( ! firstClmGokei && gokeiClm != null ) { 566 buf.append( gokeiClm ); 567 } 568 569 buf.append("</tr>").append( CR ); 570 571 // 7.0.1.7 (2019/01/21) ヘッダー作成時にオリジナルのカラムを使うので、インスタンス変数化します。 572 int sumId = 0; // 7.0.1.7 (2019/01/21) 573 final String orgLbl = useHeaderColumn ? sumClms[sumId].getLabel() : null; // sumId == 0 574 if( sumCount > 1 ) { 575 buf.append("<tr class=\"row_h\" >").append( CR ); 576 final int clmCnt = getColumnCount(); // 3.5.5.7 (2004/05/10) 577 for( int column=headCount; column<clmCnt; column++ ) { 578 if( isColumnDisplay( column ) ) { 579 if( useHeaderColumn && sumId == 0 ) { // 7.0.1.7 (2019/01/21) 580 buf.append( "<th>").append( orgLbl ).append("</th>").append( CR ); 581 } 582 else { 583 buf.append( "<th>").append( getColumnLabel(column) ).append("</th>").append( CR ); 584 } 585 } 586 sumId++; 587 if( sumId % sumCount == 0 ) { 588 sumId = 0; 589 } 590 } 591 buf.append("</tr>").append( CR ); 592 } 593 594 headerLine = buf.toString(); 595 return headerLine; 596 } 597 598 /** 599 * クロス集計結果の DBTableModelオブジェクトを作成します。 600 * 601 * @og.rev 3.5.4.8 (2004/02/23) paramInit メソッドで、初期化を行います。 602 * @og.rev 3.5.6.3 (2004/07/12) 列ヘッダーのソート可否の指定を追加 603 * @og.rev 4.0.0.0 (2007/11/27) ヘッダーカラムのエディター、レンデラー適用対応 604 * @og.rev 4.3.5.7 (2008/03/22) ↑リソースが存在しない場合は、ラベルのみ入れ替え 605 * @og.rev 5.2.2.0 (2010/11/01) useHeaderColumn,useClassAdd 属性の追加 606 * @og.rev 5.7.4.3 (2014/03/28) useHeaderColumn の適用条件を、最初の集計カラムのみに変更。 607 * @og.rev 6.4.3.2 (2016/02/19) clmKey が null の場合の処理を追加。 608 * @og.rev 7.0.1.5 (2018/12/10) useHeaderColumn=true で、横軸カラムオブジェクトを置換します。 609 * @og.rev 7.0.1.7 (2019/01/21) useHeaderResourceの判定が逆になっていた。 610 * 611 * @param table 入力もとの DBTableModelオブジェクト 612 * 613 * @return DBTableModelオブジェクト 614 * @og.rtnNotNull 615 */ 616 private DBTableModel makeCrossTable( final DBTableModel table ) { 617 final Set<String> clmData = gatSortAlgorithmSet(); 618 619 // 列のキーとなるカラムの値を取得します。 620 final int rowCnt = table.getRowCount(); // 3.5.5.7 (2004/05/10) 621 for( int row=0; row<rowCnt; row++ ) { 622 final String clm = table.getValue( row,colClmNo ); 623 if( clm.length() > 0 ) { clmData.add( clm ); } 624 } 625 // ゼロストリングは、合計行になりますので、最後に追加します。 626 627 // 3.5.6.3 (2004/07/12) ゼロストリングは、合計行になりますので、最後に追加します。 628 clmKeys = clmData.toArray( new String[clmData.size() + 1] ) ; 629 630 clmKeys[clmKeys.length-1] = "" ; 631 632 final int numberOfColumns = headCount + clmKeys.length * sumCount ; 633 634 final DBTableModel tableImpl = DBTableModelUtil.newDBTable(); 635 tableImpl.init( numberOfColumns ); 636 637 // ヘッダーカラム(ROWデータ含む)は、そのまま、設定します。 638 for( int column=0; column<headCount; column++ ) { 639 tableImpl.setDBColumn( column,table.getDBColumn(column) ); 640 } 641 642 // 列情報は、合計値のカラム定義を使用します。 643 // 7.0.1.7 (2019/01/21) ヘッダー作成時にオリジナルのカラムを使うので、インスタンス変数化します。 644// DBColumn[] dbColumn = new DBColumn[sumCount]; 645 sumClms = new DBColumn[sumCount]; 646 for( int i=0; i<sumCount; i++ ) { 647// dbColumn[i] = table.getDBColumn(headCount + 1 + i); 648 sumClms[i] = table.getDBColumn(headCount + 1 + i); 649 } 650 651 // 列情報は、列の名前をカラムの値に変えて、合計カラム列のコピー情報を設定します。 652 653 int sumId = 0; 654 final ResourceManager resource = getResourceManager(); 655 useHeaderColumn = useHeaderColumn && resource != null ; // 5.2.2.0 (2010/11/01) 656 // 5.2.2.0 (2010/11/01) useClassAdd 属性の追加 657 658 clsAdd = new String[numberOfColumns]; 659 660 // 列情報カラムは、ヘッダー分に割り当てられる為、開始が、headCount からになります。 661 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); // 6.1.0.0 (2014/12/26) refactoring 662 663 // 7.0.1.5 (2018/12/10) useHeaderColumn=true で、横軸カラムオブジェクトを置換します。 664 final DBColumn colClm = useHeaderColumn ? table2.getDBColumn( colClmNo ) : null ; // 基準となるカラムのラベル取得用カラム 665 666 for( int column=headCount; column<numberOfColumns; column++ ) { 667// DBColumn dbClm = dbColumn[sumId]; 668 DBColumn dbClm = sumClms[sumId]; // 7.0.1.7 (2019/01/21) 669 final String clmKey = clmKeys[ (column-headCount)/sumCount ]; 670 671 // 5.2.2.0 (2010/11/01) useClassAdd 属性の追加 672 if( useClassAdd ) { 673 // ※ 特殊対応:cssなどで指定できるIDやCLASS属性は、先頭文字が数字の場合は、 674 // 無効になります。(つまり、効きません。) 675 // 表示ヘッダーは、年月や、社員番号(数字)などのケースもあります。そこで、先頭が数字の 676 // 場合は、"x"(小文字のx)を自動的に頭に追加します。 677 buf.setLength(0); 678 if( clmKey != null && clmKey.length() > 0 ) { 679 final char ch = clmKey.charAt(0); 680 if( ch >= '0' && ch <= '9' ) { 681 buf.append( 'x' ); // 6.0.2.5 (2014/10/31) char を append する。 682 } 683 buf.append( clmKey ); 684 } 685 686 final String nm = dbClm.getName(); 687 if( nm != null && nm.length() > 0 ) { 688 buf.append( ' ' ); // 6.0.2.5 (2014/10/31) char を append する。 689 final char ch = nm.charAt(0); 690 if( ch >= '0' && ch <= '9' ) { 691 buf.append( 'x' ); // 6.0.2.5 (2014/10/31) char を append する。 692 } 693 buf.append( nm ); 694 } 695 clsAdd[column] = buf.toString(); 696 } 697 698 // 5.7.4.3 (2014/03/28) useHeaderColumn の適用条件を、最初の集計カラムのみに変更。 699 if( useHeaderColumn && sumId == 0 ) { 700 // 6.4.3.2 (2016/02/19) clmKey が null の場合の処理を追加。 701 if( clmKey == null || clmKey.isEmpty() ) { // 合計カラムのこと 702 final DBColumnConfig dbCfg2 = dbClm.getConfig(); 703 dbCfg2.setLabelData( resource.getLabelData( gokeiLabel ) ); 704 dbClm = new DBColumn( dbCfg2 ); 705 } 706 else { 707 final DBColumn clmTmp = resource.getDBColumn( clmKey ); // リソースがあれば優先します。 708 if( clmTmp == null ) { 709 final DBColumnConfig dbCfg2 = dbClm.getConfig(); 710 dbCfg2.setName( clmKey ); 711 // 7.0.1.7 (2019/01/21) useHeaderResourceの判定が逆になっていた。 712 if( useHeaderResource ) { // 7.0.1.5 (2018/12/10) useHeaderResource で、以前の方法 713// dbCfg2.setLabelData( resource.getLabelData( clmKey ) ); 714 dbCfg2.setLabelData( new LabelData( clmKey,colClm.getRendererValue( clmKey ) ) ); // 参照カラム 715 } 716 else { 717// dbCfg2.setLabelData( new LabelData( clmKey,colClm.getRendererValue( clmKey ) ) ); // 参照カラム 718 dbCfg2.setLabelData( resource.getLabelData( clmKey ) ); 719 } 720 dbClm = new DBColumn( dbCfg2 ); 721 } 722 else { 723 dbClm = dbClm; 724 } 725 } 726 } 727 728 tableImpl.setDBColumn( column,dbClm ); 729 730 sumId++; 731 if( sumId % sumCount == 0 ) { 732 sumId = 0; 733 } 734 } 735 736 // クロス集計データの作成 737 final CrossMap cross = new CrossMap( clmKeys,headCount,sumCount ); 738 for( int row=0; row<rowCnt; row++ ) { 739 final String[] data = table.getValues( row ); 740 cross.add( data ); 741 } 742 743 // データ部の設定 744 final int size = cross.getSize(); 745 for( int row=0; row<size; row++ ) { 746 tableImpl.addValues( cross.get( row ), row ); 747 } 748 749 tableImpl.resetModify(); 750 751 final DBTableModel model ; 752 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 753 if( gokeiSortDir == null ) { 754 model = tableImpl; 755 } 756 else { 757 final DBTableModelSorter temp = new DBTableModelSorter(); 758 temp.setModel( tableImpl ); 759 760 final boolean direction = Boolean.parseBoolean( gokeiSortDir ); // 6.1.0.0 (2014/12/26) refactoring 761 temp.sortByColumn( numberOfColumns-1,direction ); 762 model = temp ; 763 } 764 765 return model ; 766 } 767 768 /** 769 * 列ヘッダーのソート方法に応じた、Setオブジェクトを返します。 770 * ここでは、NUMBER , STRING , LOAD の3種類用意しています。 771 * 772 * @og.rev 3.5.6.3 (2004/07/12) 新規作成 773 * 774 * @return ソート方法に応じたSetオブジェクト 775 * @og.rtnNotNull 776 */ 777 private Set<String> gatSortAlgorithmSet() { 778 final Set<String> rtnSet ; 779 780 if( "LOAD".equalsIgnoreCase( cubeSortType ) ) { 781 rtnSet = new LinkedHashSet<>(); 782 } 783 else if( "NUMBER".equalsIgnoreCase( cubeSortType ) ) { 784 rtnSet = new TreeSet<>( NUMBER_SORT ); 785 } 786 else if( "STRING".equalsIgnoreCase( cubeSortType ) ) { 787 rtnSet = new TreeSet<>(); 788 } 789 else { 790 final String errMsg = "cubeSortType は、NUMBER,STRING,LOAD 以外指定できません。" + 791 " cubeSortType=[" + cubeSortType + "]"; 792 throw new HybsSystemException( errMsg ); 793 } 794 795 return rtnSet ; 796 } 797 798 /** 799 * 表示不可カラム名を、CSV形式で与えます。 800 * 例:"OYA,KO,HJO,SU,DYSET,DYUPD" 801 * null を与えた場合は,なにもしません。 802 * 803 * 注意:このクラスでは、DBTableModel を作り直すタイミングが、 804 * create メソッド実行時です。(パラメータの初期化が必要な為) 805 * よって、このメソッドは、初期が終了後に、再セットします。 806 * 807 * @og.rev 3.7.0.4 (2005/03/18) 新規作成 808 * 809 * @param columnName カラム名 810 */ 811 @Override 812 public void setNoDisplay( final String columnName ) { 813 noDisplayKeys = columnName; 814 } 815 816 /** 817 * 表示可能カラム名を、CSV形式で与えます。 818 * 例:"OYA,KO,HJO,SU,DYSET,DYUPD" 819 * setColumnDisplay( int column,boolean rw ) の簡易版です。 820 * null を与えた場合は,なにもしません。 821 * また、全カラムについて、有効にする場合は、columnName="*" を設定します。 822 * 823 * @og.rev 5.2.2.0 (2010/11/01) 新規追加 824 * 825 * @param columnName カラム名 826 */ 827 @Override 828 public void setColumnDisplay( final String columnName ) { 829 columnDisplayKeys = columnName; 830 } 831 832 /** 833 * NUMBER ソート機能(整数限定) 内部クラス 834 * これは通常のソートではなく、ヘッダーに使うラベルのソートなので、 835 * 整数のみと限定します。実数の場合は、桁合わせ(小数点以下の桁数) 836 * されているという前提です。 837 * 838 * @og.rev 3.5.6.3 (2004/07/12) 新規作成 839 */ 840 private static final class NumberComparator implements Comparator<String>,Serializable { 841 private static final long serialVersionUID = 400020050131L ; // 4.0.0.0 (2005/01/31) 842 843 /** 844 * 順序付けのために2つの引数を比較します。 845 * 846 * Comparator<String> インタフェースの実装です。 847 * 848 * @param s1 比較対象の最初のString 849 * @param s2 比較対象の2番目のString 850 * @return 最初の引数が2番目の引数より小さい場合は負の整数、両方が等しい場合は0、最初の引数が2番目の引数より大きい場合は正の整数。 851 */ 852 @Override 853 public int compare( final String s1, final String s2 ) { 854 // 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) 855 final int rtn ; 856 if( s1.length() > s2.length() ) { rtn = 1; } 857 else if( s1.length() < s2.length() ) { rtn = -1; } 858 else { 859 rtn = s1.compareTo( s2 ); 860 } 861 return rtn; 862 } 863 } 864 865 /** 866 * 表示項目の編集(並び替え)が可能かどうかを返します。 867 * 868 * @og.rev 5.1.6.0 (2010/05/01) 新規追加 869 * 870 * @return 表示項目の編集(並び替え)が可能かどうか(false:不可能) 871 */ 872 @Override 873 public boolean isEditable() { 874 return false; 875 } 876}