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 * 初期値は、{@og.value #AUTO_SKIP_MIN_COUNT} です。 057 * 058 * SKIP_SIZE は、自動設定時(DT_SKIPをマイナス)の間引き後のサイズを指定します。 059 * サイズは、大体の目安です。例えば、1300件のデータの場合、1300/SKIP_SIZE ごとに、 060 * 間引くことになります。 061 * 初期値は、{@og.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 for( int i=0; i<clmNo.length; i++ ) { 182 if( clmNo[i] >= 0 ) { 183 final String val = table.getValue( row, clmNo[i] ); 184 if( val != null && val.length() > 0 ) { 185 buf.append( val ).append( '_' ); 186 } 187 } 188 } 189 return buf.toString(); 190 } 191 192 /** 193 * 実際の間引き処理を行うクラス。 194 * 195 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 196 */ 197 private static final class OmitTable { 198 private final DBTableModel orgTable ; 199 private final DBTableModel rtnTable ; 200 private final int[] clmNos ; 201 private final int clmSize; 202 private final int skipNum; // まとめ数。min/maxの2行出すので、間引くのは2の倍数となる。 203 private final double[] minVals; 204 private final double[] maxVals; 205 private int minRow; // 最小値の置換があった最後の行番号 206 private int maxRow; // 最大値の置換があった最後の行番号 207 208 private int count; // 内部のカウンタ 209 210 /** 211 * 実際の間引き処理を行うクラスのコンストラクター 212 * 213 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 214 * 215 * @param table 間引き処理を行う、元のDBTableModel 216 * @param clmNos 最大最小処理を行うカラム番号配列 217 * @param skipDataNum 間引き数(すでに、0,マイナスは処理済) 218 * 219 */ 220 public OmitTable( final DBTableModel table , final int[] clmNos , final int skipDataNum ) { 221 this.orgTable = table; 222 this.rtnTable = table.newModel(); 223 this.clmNos = clmNos; 224 this.clmSize = clmNos.length; // 値部分のみの配列番号 225 this.skipNum = skipDataNum * 2; // まとめ数(min,max 2行作るので 226 227 minVals = new double[clmSize]; 228 maxVals = new double[clmSize]; 229 dataInit(); // データの初期化 230 } 231 232 /** 233 * 最大最小のデータの初期化を行います。 234 * 235 * 最小値に、doubleの最大値を、最大値に、daoubleの最小値を設定しておくことで、 236 * 最初の処理で、必ず、置換が発生するようにしています。 237 * データがnullのような場合には、置換が最後まで発生しないので、 238 * 行番号を、-1 に設定しておきます。 239 * 240 * @og.rev 6.9.4.1 (2018/04/09) 新規作成 241 */ 242 private void dataInit() { 243 Arrays.fill( minVals,Double.POSITIVE_INFINITY ); // 最小値の初期値は、正の無限大 244 Arrays.fill( maxVals,Double.NEGATIVE_INFINITY ); // 最大値の初期値は、負の無限大 245 minRow = -1; // 初期化:置換が無い場合のフラグを兼ねています。 246 maxRow = -1; // 初期化:置換が無い場合のフラグを兼ねています。 247 } 248 249 /** 250 * 間引き処理のための最大最小のデータ交換処理。 251 * 252 * 対象カラムは複数あるので、どれかのカラムが最小・最大値を持つ場合に、置換されます。 253 * そういう意味では、最大・最小の行番号は、最後に置換された行番号になります。 254 * 置換カラムが、一つだけの場合は、正確ですが、そうでない場合は、大体の目安程度です。 255 * 256 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 257 * 258 * @param row 処理する行番号 259 */ 260 private void change( final int row ) { 261 final String[] vals = orgTable.getValues( row ); 262 for( int i=0; i<clmSize; i++ ) { 263 final String val = vals[clmNos[i]]; 264 if( !StringUtil.isNull( val ) ) { // データが null の場合は、置換が発生しません。 265 final double dval = Double.parseDouble( val ); 266 if( minVals[i] > dval ) { minVals[i] = dval; minRow = row; } // 最小値の入れ替え 267 if( maxVals[i] < dval ) { maxVals[i] = dval; maxRow = row; } // 最大値の入れ替え 268 } 269 } 270 } 271 272 /** 273 * 間引き処理のための最大最小のデータチェック。 274 * 275 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 276 * @og.rev 6.9.4.1 (2018/04/09) 最大、最小の行の追加は、行番号の順番に行います。 277 * 278 * @param row 処理する行番号 279 * @param isBreak ブレイク行かどうか 280 */ 281 public void check( final int row , final boolean isBreak ) { 282 change( row ); 283 284 count++ ; // 先に、カウントアップしておくのは、 == 0 のときに、出力処理を行わないようにするため。 285 286 if( isBreak || ( count % skipNum == 0 ) ) { // 間引き処理(出力処理) 287 final String[] old1 = orgTable.getValues( minRow < 0 ? row : minRow ); // 最小値 288 final String[] old2 = orgTable.getValues( maxRow < 0 ? row : maxRow ); // 最大値 289 for( int i=0; i<clmSize; i++ ) { // 指定のカラムだけ、置き換えます。 290 final int cno = clmNos[i]; 291 if( minVals[i] != Double.POSITIVE_INFINITY ) { // 置換があったカラムのみ置き換える。 292 old1[cno] = String.valueOf( minVals[i] ); 293 } 294 if( maxVals[i] != Double.NEGATIVE_INFINITY ) { // 置換があったカラムのみ置き換える。 295 old2[cno] = String.valueOf( maxVals[i] ); 296 } 297 } 298 299 // 行番号の順番どおりに挿入します。 300 if( minRow < maxRow ) { 301 rtnTable.addColumnValues( old1 ); 302 rtnTable.addColumnValues( old2 ); 303 } 304 else { 305 rtnTable.addColumnValues( old2 ); 306 rtnTable.addColumnValues( old1 ); 307 } 308 309 dataInit(); // データの初期化 310 } 311 312 if( isBreak ) { count = 0; } 313 } 314 315 /** 316 * 間引き処理の結果のDBTableModelを返します。 317 * 318 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 319 * 320 * @return 間引き処理を行った、DBTableModel 321 */ 322 public DBTableModel getTable() { return rtnTable; } 323 } 324}