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}