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.table; 017 018import java.util.Arrays; 019 020import org.opengion.fukurou.util.StringUtil; 021import org.opengion.hayabusa.db.AbstractTableFilter; 022// import org.opengion.hayabusa.db.DBColumn; 023import org.opengion.hayabusa.db.DBTableModel; 024// import org.opengion.hayabusa.db.DBTableModelUtil; 025// import org.opengion.hayabusa.resource.ResourceManager; 026 027// import static org.opengion.plugin.table.StandardDeviation.ADD_CLMS; 028 029/** 030 * TableFilter_SKIPROW は、TableFilter インターフェースを継承した、DBTableModel 処理用の 031 * 実装クラスです。 032 * グラフ等で使用する、数字カラムをもつデータを、間引きます。 033 * 034 * DT_SKIP は、間引き方を指定します。 035 * マイナス:自動的に間引き率を設定します。(初期値) 036 * SKIP_MIN_COUNT 以上のデータ件数の場合、SKIP_SIZE に近い数字に 037 * なるように間引きます。 038 * 0 :間引き処理を行いません。 039 * 整数 :間引く数を指定します。例えば、10 を指定すると、10行が、1行になるように、間引きます。 040 * 正確には、20行ごとに、min行とmax行の2行を出力します。 041 * 042 * GROUP_KEY は、数値とは関係の無い、固定値のカラムをCSV形式で指定します。 043 * このキーワード群を固まり(グループ)として、間引き処理の初期化を行います。 044 * キーブレイクごとに、間引きの開始を行います。 045 * なお、先の間引きの間隔は、キーブレイクに関係なく、全体の件数に対して判定されます。 046 * キーのグループの件数が少ない場合は、最小2件のデータのみ出力されます。 047 * 048 * VAL_CLMS は、数値カラムで、グラフ等の値を持っているカラムをCSV形式で指定します。 049 * これは、間引き処理で、最小値と最大値のレコードを作り直します。 050 * グラフ等で間引くと、最大値や最小値などが、消えてなくなる可能性がありますが、 051 * このフィルターでは、各カラムのの中で、最大値だけを集めたレコードと、最小値だけを集めた 052 * レコードを作ります。 053 * 054 * SKIP_MIN_COUNT は、自動設定時(DT_SKIPをマイナス)にセットした場合の、データの最小単位です。 055 * この件数以下の場合は、自動間引きの場合は、間引き処理を行いません。 056 * 初期値は、{@value #AUTO_SKIP_MIN_COUNT} です。 057 * 058 * SKIP_SIZE は、自動設定時(DT_SKIPをマイナス)の間引き後のサイズを指定します。 059 * サイズは、大体の目安です。例えば、1300件のデータの場合、1300/SKIP_SIZE ごとに、 060 * 間引くことになります。 061 * 初期値は、{@value #AUTO_SKIP_SIZE} です。 062 * 063 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。 064 * 【パラメータ】 065 * { 066 * DT_SKIP : まとめ数(-1:自動(初期値)、0:まとめなし、数値:まとめ数 067 * GROUP_KEY : グループカラム (複数指定可) 068 * VAL_CLMS : まとめるに当たって、最大、最小判定を行うカラム列 069 * SKIP_MIN_COUNT : 自動設定時にセットした場合の、データの最小単位 070 * SKIP_SIZE : 自動設定時の間引き後のサイズ(の目安) 071 * } 072 * 073 * @og.formSample 074 * ●形式: 075 * ① <og:tableFilter classId="SKIPROW" selectedAll="true" 076 * keys="GROUP_KEY,DT_SKIP,VAL_CLMS" 077 * vals='"SYSTEM_ID,USERID",36,"USED_TIME,CNT_ACCESS,CNT_READ,TM_TOTAL_QUERY"' /> 078 * 079 * ② <og:tableFilter classId="SKIPROW" selectedAll="true" > 080 * { 081 * GROUP_KEY : SYSTEM_ID,USERID ; 082 * DT_SKIP : 36 ; 083 * VAL_CLMS : USED_TIME,CNT_ACCESS,CNT_READ,TM_TOTAL_QUERY ; 084 * } 085 * </og:tableFilter> 086 * 087 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 088 * 089 * @version 0.9.0 2000/10/17 090 * @author Hiroki Nakamura 091 * @since JDK1.1, 092 */ 093public class TableFilter_SKIPROW extends AbstractTableFilter { 094 /** このプログラムのVERSION文字列を設定します。 {@value} */ 095 private static final String VERSION = "6.9.3.0 (2018/03/26)" ; 096 097 /** skipDataNum の自動設定時の、最小設定行数 {@value} **/ 098 public static final int AUTO_SKIP_MIN_COUNT = 1000; 099 /** skipDataNum の自動設定時の、間引き後のサイズ {@value} **/ 100 public static final int AUTO_SKIP_SIZE = 500; 101 102 private DBTableModel table ; 103 104 /** 105 * デフォルトコンストラクター 106 */ 107 public TableFilter_SKIPROW() { 108 super(); 109 initSet( "DT_SKIP" , "まとめ数(-1:自動(初期値)、0:まとめなし、数値:まとめ数" ); 110 initSet( "GROUP_KEY" , "グループカラム (複数指定可)" ); 111 initSet( "VAL_CLMS" , "まとめるに当たって、最大、最小判定を行うカラム列" ); 112 initSet( "SKIP_MIN_COUNT" , "自動設定時にセットした場合の、データの最小単位" ); 113 initSet( "SKIP_SIZE" , "自動設定時の間引き後のサイズ(の目安)" ); 114 } 115 116 /** 117 * DBTableModel処理を実行します。 118 * 119 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 120 * 121 * @return 処理結果のDBTableModel 122 */ 123 public DBTableModel execute() { 124 table = getDBTableModel(); 125 126 int skipDataNum = StringUtil.nval( getValue( "DT_SKIP" ) , -1 ); 127 final int skipMinCnt = StringUtil.nval( getValue( "SKIP_MIN_COUNT" ) , AUTO_SKIP_MIN_COUNT ); 128 final int skipSize = StringUtil.nval( getValue( "SKIP_SIZE" ) , AUTO_SKIP_SIZE ); 129 130 final int ROW_CNT = table.getRowCount(); 131 if( skipDataNum < 0 ) { // < 0 は、自動設定。 132 skipDataNum = ROW_CNT < skipMinCnt 133 ? 0 // (AUTO_SKIP_SIZE)件以下の場合は、間引き処理を行わない。 134 : ROW_CNT / skipSize ; // (AUTO_SKIP_SIZE)件を目安に数を減らします。 135 } 136 if( skipDataNum == 0 ) { return table; } // 0 は、まとめなし 137 138 final String[] grpClm = StringUtil.csv2Array( getValue( "GROUP_KEY" ) ); 139 // グループカラムのカラム番号を求めます。 140 final int[] grpNos = getTableColumnNo( grpClm ); 141 142 final String[] valClms = StringUtil.csv2Array( getValue( "VAL_CLMS" ) ); 143 // 最大、最小判定を行うカラム番号を求めます。 144 final int[] clmNos = getTableColumnNo( valClms ); 145 146 // まとめ処理を行った結果を登録するテーブルモデル 147 148 final OmitTable omitTbl = new OmitTable( table , clmNos , skipDataNum ); 149 150 String bkKey = getSeparatedValue( 0, grpNos ); // row==0 ブレイクキー 151 omitTbl.check( 0 , false ); // row==0 最初の行は、初期値設定 152 153 // 1回目は初期設定しておく(row=1)。 154 for( int row=1; row<ROW_CNT; row++ ) { 155 final String rowKey = (row+1)==ROW_CNT ? "" : getSeparatedValue( row, grpNos ); // 最後の列は、ブレイクさせる。 156 if( bkKey.equals( rowKey ) ) { // 前と同じ(継続) 157 omitTbl.check( row , false ); 158 } 159 else { // キーブレイク 160 omitTbl.check( row , true ); 161 bkKey = rowKey; 162 } 163 } 164 165 return omitTbl.getTable(); 166 } 167 168 /** 169 * 各行のキーとなるキーカラムの値を連結した値を返します。 170 * 171 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 172 * 173 * @param row 行番号 174 * @param clmNo カラム番号配列 175 * 176 * @return 各行のキーとなるキーカラムの値を連結した値 177 * @og.rtnNotNull 178 */ 179 private String getSeparatedValue( final int row, final int[] clmNo ) { 180 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 181 // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop 182 for( final int clm : clmNo ) { 183 if( clm >= 0 ) { 184 final String val = table.getValue( row, clm ); 185// for( int i=0; i<clmNo.length; i++ ) { 186// if( clmNo[i] >= 0 ) { 187// final String val = table.getValue( row, clmNo[i] ); 188 if( val != null && val.length() > 0 ) { 189 buf.append( val ).append( '_' ); 190 } 191 } 192 } 193 return buf.toString(); 194 } 195 196 /** 197 * 実際の間引き処理を行うクラス。 198 * 199 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 200 */ 201 private static final class OmitTable { 202 private final DBTableModel orgTable ; 203 private final DBTableModel rtnTable ; 204 private final int[] clmNos ; 205 private final int clmSize; 206 private final int skipNum; // まとめ数。min/maxの2行出すので、間引くのは2の倍数となる。 207 private final double[] minVals; 208 private final double[] maxVals; 209 private int minRow; // 最小値の置換があった最後の行番号 210 private int maxRow; // 最大値の置換があった最後の行番号 211 212 private int count; // 内部のカウンタ 213 214 /** 215 * 実際の間引き処理を行うクラスのコンストラクター 216 * 217 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 218 * 219 * @param table 間引き処理を行う、元のDBTableModel 220 * @param clmNos 最大最小処理を行うカラム番号配列 221 * @param skipDataNum 間引き数(すでに、0,マイナスは処理済) 222 * 223 */ 224 public OmitTable( final DBTableModel table , final int[] clmNos , final int skipDataNum ) { 225 this.orgTable = table; 226 this.rtnTable = table.newModel(); 227 this.clmNos = clmNos; 228 this.clmSize = clmNos.length; // 値部分のみの配列番号 229 this.skipNum = skipDataNum * 2; // まとめ数(min,max 2行作るので 230 231 minVals = new double[clmSize]; 232 maxVals = new double[clmSize]; 233 dataInit(); // データの初期化 234 } 235 236 /** 237 * 最大最小のデータの初期化を行います。 238 * 239 * 最小値に、doubleの最大値を、最大値に、daoubleの最小値を設定しておくことで、 240 * 最初の処理で、必ず、置換が発生するようにしています。 241 * データがnullのような場合には、置換が最後まで発生しないので、 242 * 行番号を、-1 に設定しておきます。 243 * 244 * @og.rev 6.9.4.1 (2018/04/09) 新規作成 245 */ 246 private void dataInit() { 247 Arrays.fill( minVals,Double.POSITIVE_INFINITY ); // 最小値の初期値は、正の無限大 248 Arrays.fill( maxVals,Double.NEGATIVE_INFINITY ); // 最大値の初期値は、負の無限大 249 minRow = -1; // 初期化:置換が無い場合のフラグを兼ねています。 250 maxRow = -1; // 初期化:置換が無い場合のフラグを兼ねています。 251 } 252 253 /** 254 * 間引き処理のための最大最小のデータ交換処理。 255 * 256 * 対象カラムは複数あるので、どれかのカラムが最小・最大値を持つ場合に、置換されます。 257 * そういう意味では、最大・最小の行番号は、最後に置換された行番号になります。 258 * 置換カラムが、一つだけの場合は、正確ですが、そうでない場合は、大体の目安程度です。 259 * 260 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 261 * 262 * @param row 処理する行番号 263 */ 264 private void change( final int row ) { 265 final String[] vals = orgTable.getValues( row ); 266 for( int i=0; i<clmSize; i++ ) { 267 final String val = vals[clmNos[i]]; 268 if( !StringUtil.isNull( val ) ) { // データが null の場合は、置換が発生しません。 269 final double dval = Double.parseDouble( val ); 270 if( minVals[i] > dval ) { minVals[i] = dval; minRow = row; } // 最小値の入れ替え 271 if( maxVals[i] < dval ) { maxVals[i] = dval; maxRow = row; } // 最大値の入れ替え 272 } 273 } 274 } 275 276 /** 277 * 間引き処理のための最大最小のデータチェック。 278 * 279 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 280 * @og.rev 6.9.4.1 (2018/04/09) 最大、最小の行の追加は、行番号の順番に行います。 281 * 282 * @param row 処理する行番号 283 * @param isBreak ブレイク行かどうか 284 */ 285 public void check( final int row , final boolean isBreak ) { 286 change( row ); 287 288 count++ ; // 先に、カウントアップしておくのは、 == 0 のときに、出力処理を行わないようにするため。 289 290 if( isBreak || ( count % skipNum == 0 ) ) { // 間引き処理(出力処理) 291 final String[] old1 = orgTable.getValues( minRow < 0 ? row : minRow ); // 最小値 292 final String[] old2 = orgTable.getValues( maxRow < 0 ? row : maxRow ); // 最大値 293 for( int i=0; i<clmSize; i++ ) { // 指定のカラムだけ、置き換えます。 294 final int cno = clmNos[i]; 295 if( minVals[i] != Double.POSITIVE_INFINITY ) { // 置換があったカラムのみ置き換える。 296 old1[cno] = String.valueOf( minVals[i] ); 297 } 298 if( maxVals[i] != Double.NEGATIVE_INFINITY ) { // 置換があったカラムのみ置き換える。 299 old2[cno] = String.valueOf( maxVals[i] ); 300 } 301 } 302 303 // 行番号の順番どおりに挿入します。 304 if( minRow < maxRow ) { 305 rtnTable.addColumnValues( old1 ); 306 rtnTable.addColumnValues( old2 ); 307 } 308 else { 309 rtnTable.addColumnValues( old2 ); 310 rtnTable.addColumnValues( old1 ); 311 } 312 313 dataInit(); // データの初期化 314 } 315 316 if( isBreak ) { count = 0; } 317 } 318 319 /** 320 * 間引き処理の結果のDBTableModelを返します。 321 * 322 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 323 * 324 * @return 間引き処理を行った、DBTableModel 325 */ 326 public DBTableModel getTable() { return rtnTable; } 327 } 328}