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}