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; 022import org.opengion.hayabusa.db.DBColumn; 023import org.opengion.hayabusa.db.DBTableModel; 024import org.opengion.hayabusa.db.DBTableModelUtil; 025import org.opengion.hayabusa.resource.ResourceManager; 026 027import 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 final DBTableModel orgTable ; 199 final DBTableModel rtnTable ; 200 final int[] clmNos ; 201 final int clmSize; 202 final int skipNum; // まとめ数。min/maxの2行出すので、間引くのは2の倍数となる。 203 final double[] minVals; 204 final double[] maxVals; 205 int minRow = 0; // 最小値の置換があった最後のレコード 206 int maxRow = 0; // 最大値の置換があった最後のレコード 207 208 int count = 0; // 内部のカウンタ 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 Arrays.fill( minVals,Double.POSITIVE_INFINITY ); // 最小値の初期値は、正の無限大 230 Arrays.fill( maxVals,Double.NEGATIVE_INFINITY ); // 最大値の初期値は、負の無限大 231 } 232 233 /** 234 * 間引き処理のための最大最小のデータ交換処理 235 * 236 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 237 * 238 * @param row 処理する行番号 239 */ 240 private void change( final int row ) { 241 final String[] vals = orgTable.getValues( row ); 242 for( int i=0; i<clmSize; i++ ) { 243 final String val = vals[clmNos[i]]; 244 if( !StringUtil.isNull( val ) ) { 245 final double dval = Double.parseDouble( val ); 246 if( minVals[i] > dval ) { minVals[i] = dval; minRow = row; } // 最小値の入れ替え 247 if( maxVals[i] < dval ) { maxVals[i] = dval; maxRow = row; } // 最大値の入れ替え 248 } 249 } 250 } 251 252 /** 253 * 間引き処理のための最大最小のデータチェック 254 * 255 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 256 * 257 * @param row 処理する行番号 258 * @param isBreak ブレイク行かどうか 259 */ 260 public void check( final int row , final boolean isBreak ) { 261 change( row ); 262 263 count++ ; // 先に、カウントアップしておくのは、 == 0 のときに、出力処理を行わないようにするため。 264 265 if( isBreak || ( count % skipNum == 0 ) ) { // 間引き処理(出力処理) 266 final String[] old1 = orgTable.getValues( minRow ); // 最小値 267 for( int i=0; i<clmSize; i++ ) { 268 old1[clmNos[i]] = String.valueOf( minVals[i] ); 269 } 270 rtnTable.addColumnValues( old1 ); 271 272 final String[] old2 = orgTable.getValues( maxRow ); // 最大値 273 for( int i=0; i<clmSize; i++ ) { 274 old2[clmNos[i]] = String.valueOf( maxVals[i] ); 275 } 276 rtnTable.addColumnValues( old2 ); 277 278 Arrays.fill( minVals,Double.POSITIVE_INFINITY ); // 次の塊で判定するため、初期化する。 279 Arrays.fill( maxVals,Double.NEGATIVE_INFINITY ); // 次の塊で判定するため、初期化する。 280 } 281 282 if( isBreak ) { count = 0; } 283 } 284 285 /** 286 * 間引き処理の結果のDBTableModelを返します。 287 * 288 * @og.rev 6.9.3.0 (2018/03/26) 新規作成 289 * 290 * @return 間引き処理を行った、DBTableModel 291 */ 292 public DBTableModel getTable() { return rtnTable; } 293 } 294}