    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 * キーのｸﾞﾙｰﾌﾟの件数が少ない場合は、最小２件のﾃﾞｰﾀのみ出力されます。
    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 *      ① &lt;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"' /&gt;
    078 *
    079 *      ② &lt;og:tableFilter classId="SKIPROW"  selectedAll="true" &gt;
    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 *         &lt;/og:tableFilter&gt;
    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                // １回目は初期設定しておく(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の２行出すので、間引くのは２の倍数となる。
    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}