    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.Map;
    019import java.util.HashMap;
    020
    021import java.util.List;                                                                                  // 8.0.0.1 (2021/10/08)
    022import java.util.ArrayList;                                                                             // 8.0.0.1 (2021/10/08)
    023
    024import org.opengion.fukurou.util.StringUtil;
    025// import org.opengion.hayabusa.common.HybsSystem;                              // 8.0.0.0 (2021/09/30)
    026import org.opengion.hayabusa.db.AbstractTableFilter;
    027import org.opengion.hayabusa.db.DBColumn;
    028import org.opengion.hayabusa.db.DBColumnConfig;
    029import org.opengion.hayabusa.db.DBTableModel;
    030import org.opengion.hayabusa.db.DBTableModelUtil;
    031import org.opengion.hayabusa.resource.ResourceManager;
    032import org.opengion.hayabusa.html.ViewMarker;                                   // 8.0.0.0 (2021/09/30)
    033
    034/**
    035 * TableFilter_ROTATE は、TableFilter インターフェースを継承した、DBTableModel 処理用の
    036 * 実装クラスです。
    037 *
    038 * ここではテーブルの回転<del>及びその逆回転</del>を行います。
    039 *   8.0.0.0 (2021/07/31) 逆回転 廃止
    040 *
    041 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
    042 * 【パラメータ】
    043 *  {
    044 *       KEY_CLM    : キーカラム(複数指定可)      (必須)
    045 *       ROTATE_CLM : 回転するカラム              (必須)
    046 *       ROTATE_LBL : 回転カラムのヘッダーラベル  (任意指定)                                             8.0.0.1 (2021/10/08)
    047 *       VALUE_CLM  : 回転カラムの値              (必須)
    048 *       USE_LABEL  : 値ラベルのカラムを生成するか (任意指定 初期値:false)                   8.0.0.0 (2021/07/31)
    049 *       USE_RENDERER: 値の表示に、renndererを使用するか (任意指定 初期値:false)   8.0.0.0 (2021/07/31)
    050 *       USE_MARKER : 値の表示に、viewMarkerを使用するか (任意指定 初期値:false)   8.0.0.0 (2021/09/30)
    051 *  <del>REVERSE    : 回転(false)・逆回転(true)   (任意指定 初期値:false)</del>              8.0.0.0 (2021/07/31) 廃止
    052 *       MUST_CLM   : 必須属性を定義するカラム    (任意指定 初期値:false)
    053 *       DEF_CLM    : 初期値を定義するカラム      (任意指定)
    054 *  }
    055 *
    056 *  ※ それぞれに指定されたカラム名が存在しない場合は、処理されませんのでご注意下さい。
    057 *
    058 * ①回転
    059 *  キーカラム(KEY_CLM)に指定された値が同じN行を1行として回転します。
    060 *  (キーカラムの値がブレイクしたタイミングで、行を変更します)
    061 *  このN行に含まれる回転カラムの値がカラム名に、回転カラム値が各カラムの値になります。
    062 *  キーカラムは、CSV形式で複数指定可能です。
    063 *
    064 *  生成されたテーブルモデルのカラムは、始めのMカラムがキーカラムに、その後ろのNカラムが
    065 *  回転されたカラムになります。
    066 *
    067 *  生成されたテーブルのカラムオブジェクトは、ROTATE_CLM で指定されたキーで生成されます。
    068 *  リソースに存在しない場合は、ROTATE_LBL で、ラベルを指定することができます。
    069 *
    070 *  また、元テーブルにMUST_CLMにより、各カラムの必須属性を定義することが
    071 *  できます。(MUST属性は、'1'又は'true'の場合に必須になります。)
    072 *
    073 * 8.0.0.0 (2021/07/31)
    074 *  USE_LABEL="true" を指定した場合、VALUE_CLM のラベルを、キーカラムと回転カラムの間に
    075 *  追加します。これは、VALUE_CLM を 複数指定できる機能追加に伴う処置です。
    076 *
    077 * 8.0.0.0 (2021/07/31) 逆回転 廃止
    078 * <del>②逆回転
    079 *  回転時の逆の挙動になります。
    080 *  "キーカラムに指定されたカラム以外"を回転カラムで指定されたカラムの値として分解します。
    081 *  各回転カラムの値は、回転カラム値に指定されたカラムに格納されます。
    082 *
    083 *  分解後のカラム数は、キーカラム数 + 2 (回転カラム、回転カラム値)になります。
    084 *  また、行数は、(分解前の行数) x (回転カラム数)になります。
    085 *</del>
    086 *
    087 * @og.formSample
    088 * ●形式：
    089 *      ① &lt;og:tableFilter classId="ROTATE" selectedAll="true"
    090 *                   keys="KEY_CLM,ROTATE_CLM,VALUE_CLM" vals='"GOKI,MAX_SID,MAX_TM_RPS",TOKEN,X_VAL' /&gt;
    091 *
    092 *      ② &lt;og:tableFilter classId="ROTATE"  selectedAll="true" &gt;
    093 *               {
    094 *                   KEY_CLM    : GOKI,MAX_SID,MAX_TM_RPS ;
    095 *                   ROTATE_CLM : TOKEN ;
    096 *                   VALUE_CLM  : X_VAL ;
    097 *               }
    098 *         &lt;/og:tableFilter&gt;
    099 *
    100 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
    101 * @og.rev 8.0.0.0 (2021/07/31) VALUE_CLMの複数指定対応と、USE_LABEL属性の追加
    102 *
    103 * @version  0.9.0  2000/10/17
    104 * @author   Hiroki Nakamura
    105 * @since    JDK1.1,
    106 */
    107public class TableFilter_ROTATE extends AbstractTableFilter {
    108        // * このプログラムのVERSION文字列を設定します。 {@value} */
    109        private static final String VERSION = "8.0.0.1 (2021/10/08)" ;
    110
    111        private DBTableModel    table    ;                      // 5.5.2.6 (2012/05/25) 共通に使うため、変数定義
    112        private ResourceManager resource ;                      // 5.5.2.6 (2012/05/25) 共通に使うため、変数定義
    113
    114        /**
    115         * デフォルトコンストラクター
    116         *
    117         * @og.rev 6.4.1.1 (2016/01/16) keysMap を、サブクラスから設定させるように変更。
    118         */
    119        public TableFilter_ROTATE() {
    120                super();
    121                initSet( "KEY_CLM"      , "キーカラム(複数指定可)(必須)"                    );
    122                initSet( "ROTATE_CLM"   , "回転するカラム(必須)"                                 );
    123                initSet( "ROTATE_LBL"   , "回転カラムのヘッダーラベル"                               );              // 8.0.0.1 (2021/10/08)
    124                initSet( "VALUE_CLM"    , "回転カラムの値(複数指定可)(必須)"          );
    125                initSet( "USE_LABEL"    , "値ラベルのカラムを生成するか(初期値:false)"   );      // 8.0.0.0 (2021/07/31)
    126                initSet( "USE_RENDERER" , "値の表示にrenndererを使用するか(初期値:false)" );  // 8.0.0.0 (2021/07/31)
    127                initSet( "USE_MARKER"   , "値の表示にviewMarkerを使用するか(初期値:false)" ); // 8.0.0.0 (2021/09/30)
    128//              initSet( "REVERSE"              , "回転(false)/逆回転(true) (初期値:false)"     );      // 8.0.0.0 (2021/07/31) 廃止
    129                initSet( "MUST_CLM"             , "必須属性を定義するカラム (初期値:false)"    );
    130                initSet( "DEF_CLM"              , "初期値を定義するカラム"                                 );
    131        }
    132
    133        /**
    134         * DBTableModel処理を実行します。
    135         *
    136         * @og.rev 4.3.7.4 (2009/07/01) 新規追加
    137         * @og.rev 5.5.2.6 (2012/05/25) protected変数を、private化したため、getterメソッドで取得するように変更
    138         *
    139         * @return 処理結果のDBTableModel
    140         */
    141        public DBTableModel execute() {
    142                table    = getDBTableModel();           // 5.5.2.6 (2012/05/25) インターフェースにgetterメソッド追加
    143                resource = getResource();                       // 5.5.2.6 (2012/05/25) インターフェースにgetterメソッド追加
    144
    145                return getRotateTable();
    146        }
    147
    148        /**
    149         * 回転後のDBTableModelを返します。
    150         *
    151         * @og.rev 5.1.8.0 (2010/07/01) メソッド名変更(setDefValue ⇒ setDefault)
    152         * @og.rev 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
    153         * @og.rev 8.0.0.0 (2021/07/31) VALUE_CLMの複数指定対応と、USE_LABEL属性の追加
    154         * @og.rev 8.0.0.1 (2021/10/08) ROTATE_LBL(回転カラムのヘッダーラベル)の追加
    155         *
    156         * @return 回転後のDBTableModel
    157         */
    158        private DBTableModel getRotateTable() {
    159                // 8.0.0.0 (2021/07/31) VALUE_CLMの複数指定対応と、USE_LABEL属性の追加
    160                // ｴﾗｰ時の原因表示を入れておきます。
    161
    162                final String tmpKeyClm  = getValue( "KEY_CLM" );
    163                final String[] keyClm   = StringUtil.csv2Array( tmpKeyClm );
    164                if( keyClm.length == 0 ) { System.out.println( "KEY_CLM is none[" + tmpKeyClm + "]" ); }
    165
    166                final String tmpRotClm  = getValue( "ROTATE_CLM" );
    167                final int rotateNo              = table.getColumnNo( tmpRotClm );               // 8.0.0.0 (2021/09/30) ｴﾗｰにします。
    168
    169                // 8.0.0.1 (2021/10/08) ROTATE_LBL(回転カラムのヘッダーラベル)の追加
    170                final String tmpRotLbl  = getValue( "ROTATE_LBL" );
    171                final int lblNo                 = table.getColumnNo( tmpRotLbl, false );        // 任意なので;
    172
    173                final String tmpValClm  = getValue( "VALUE_CLM" );
    174                final String[] valClm   = StringUtil.csv2Array( tmpValClm );
    175                if( valClm.length == 0 ) { System.out.println( "VALUE_CLM is none[" + tmpValClm + "]" ); }
    176
    177                int clmCount = 0; // 回転後のカラム数
    178                // キーカラムのカラム番号を求め、カラム数としてカウントします。
    179                final Map<String, Integer> clmMap = new HashMap<>();
    180                final int[] keyNos = new int[keyClm.length];
    181                for( int i=0; i<keyNos.length; i++ ) {
    182                        keyNos[i] = table.getColumnNo( keyClm[i] );                                     // 8.0.0.0 (2021/09/30) ｴﾗｰにします。
    183                        clmMap.put( keyClm[i], clmCount++ );                    // 固定ｶﾗﾑ
    184                }
    185
    186                // 8.0.0.0 (2021/07/31) USE_LABEL属性の追加
    187                final boolean useValLbl = StringUtil.nval( getValue( "USE_LABEL" ), false );
    188                if( useValLbl ) {
    189                        clmMap.put( "LABEL", clmCount++ );                              // ﾗﾍﾞﾙｶﾗﾑ
    190                }
    191
    192                final int valStartNo = clmCount ;                                       // 値ｶﾗﾑ(回転ｶﾗﾑ)の開始ｱﾄﾞﾚｽ
    193                final List<String> lblList = new ArrayList<>();         // 8.0.0.1 (2021/10/08) ROTATE_LBL(未使用時は、nullをｾｯﾄする)
    194                for( int i=0; i<valStartNo; i++ ) { lblList.add( null ); }      // 後の処理を共通にするため。
    195
    196                // 8.0.0.0 (2021/07/31) USE_RENDERER属性の追加
    197                final boolean useValRend = StringUtil.nval( getValue( "USE_RENDERER" ), false );
    198                // 8.0.0.0 (2021/09/30) USE_MARKER属性の追加
    199                final boolean useMarker = StringUtil.nval( getValue( "USE_MARKER" ), false );
    200                final ViewMarker viewMarker = useMarker ? getViewMarker() : null;
    201
    202                // 値カラムのカラム番号を求める。(カラム数としてカウントしない=行が増える)
    203                final int[] valNos = new int[valClm.length];
    204                final DBColumn[] valClmns = new DBColumn[valClm.length];
    205                for( int i=0; i<valNos.length; i++ ) {
    206                        valNos[i] = table.getColumnNo( valClm[i] );                                     // 8.0.0.0 (2021/09/30) ｴﾗｰにします。
    207                        if( useValRend ) {
    208                                valClmns[i] = table.getDBColumn( valNos[i] );
    209                        }
    210                }
    211
    212                int rowCount = 0; // 回転後の行数(KEY_CLMで指定したﾕﾆｰｸになる行数で、実際の行数は、VALUE_CLM 倍になる)
    213                // 回転カラムの値から回転後のカラム数を求めます。
    214                // また同時に、キーカラムの値のブレイク数により行数を求めます。
    215                final Map<String, Integer> rowMap               = new HashMap<>();
    216                final Map<String, Boolean> mustMap              = new HashMap<>();
    217                final Map<String, String>  defaultMap   = new HashMap<>();
    218                final int mustNo = table.getColumnNo( getValue( "MUST_CLM"), false );   // 任意なので
    219                final int defNo  = table.getColumnNo( getValue( "DEF_CLM" ), false );   // 任意なので
    220                for( int i=0; i<table.getRowCount(); i++ ) {
    221                        final String clmKey = table.getValue( i, rotateNo );
    222                        if( clmMap.get( clmKey ) == null ) {
    223                                clmMap.put( clmKey, clmCount++ );                       // 回転ｶﾗﾑ
    224                                // 8.0.0.1 (2021/10/08) ROTATE_LBL(未使用時は、nullをｾｯﾄする)
    225                                lblList.add( lblNo >= 0 ? table.getValue( i, lblNo ) : null );  // 後の処理を共通にするため。
    226                        }
    227                        // 必須カラム抜き出し
    228                        if( mustNo > -1 && StringUtil.nval( table.getValue( i, mustNo ), false ) ) {
    229                                mustMap.put( clmKey, true );
    230                        }
    231                        // デフォルト値を書き換えるカラムの抜き出し
    232                        if( defNo > -1 && table.getValue( i, defNo ) != null && table.getValue( i, defNo ).length() > 0 ) {
    233                                defaultMap.put( clmKey, table.getValue( i, defNo ) );
    234                        }
    235
    236                        final String rowKey = getSeparatedValue( i, keyNos );
    237                        // 6.0.0.1 (2014/04/25) These nested if statements could be combined
    238                        if( rowKey != null && rowKey.length() > 0 && rowMap.get( rowKey ) == null ) {
    239                                rowMap.put( rowKey, rowCount++ );
    240                        }
    241                }
    242
    243                // 回転後のカラム一覧よりDBTableModelを初期化します。
    244                // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
    245                final String names[] = new String[clmMap.size()];
    246                // 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
    247                clmMap.forEach( (k,v) -> names[v] = k );
    248
    249                final DBTableModel nTable = DBTableModelUtil.newDBTable();
    250                nTable.init( names.length );
    251                for( int i=0; i<names.length; i++ ) {
    252                        if( mustMap.get( names[i] ) != null ) {
    253                                table.addMustType( i, "must" );
    254                        }
    255//                      DBColumn column = resource.makeDBColumn( names[i] );
    256                        DBColumn column = resource.makeDBColumn( names[i],lblList.get(i) );                     // 8.0.0.1 (2021/10/08) ﾍｯﾀﾞｰのﾗﾍﾞﾙ。未使用時は null
    257                        // 8.0.0.0 (2021/07/31) ｵﾘｼﾞﾅﾙが数字ﾀｲﾌﾟだと、ｴﾗｰになる可能性があるため
    258                        final DBColumnConfig dbConfig = column.getConfig();                     // 固定ｶﾗﾑより値ｶﾗﾑの方が多いので、処理速度は気にしないことにする。
    259                        if( valStartNo <= i ) {                 // 値ｶﾗﾑ(回転ｶﾗﾑ)
    260                                dbConfig.setRenderer( "LABEL" );                                                //
    261                        }
    262
    263                        if( defaultMap.get( names[i] ) != null ) {
    264                                dbConfig.setDefault( defaultMap.get( names[i] ) );              // 5.1.8.0 (2010/07/01)
    265                        }
    266                        column = new DBColumn( dbConfig );                                                      // 8.0.0.0 (2021/07/31)
    267                        nTable.setDBColumn( i, column );                                                        // 5.1.8.0 (2010/07/01)
    268                }
    269
    270                // 値の一覧を作成し、DBTableModelに値をセットします。
    271                if( rowCount > 0 ) {
    272                        // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
    273                        final String[][] vals = new String[rowCount*valClm.length][names.length];       // 行数は、valClm.length 倍になる
    274                        for( int i=0; i<table.getRowCount(); i++ ) {
    275                                final int row = rowMap.get( getSeparatedValue( i, keyNos ) );
    276                                final int clm = clmMap.get( table.getValue( i, rotateNo ) );
    277
    278                                final int nrow = row * valClm.length ;
    279                                // 8.0.0.0 (2021/07/31) VALUE_CLM が複数存在した場合の処理
    280                                for( int k=0; k<valNos.length; k++ ) {
    281                                        for( int j=0; j<keyNos.length; j++ ) {
    282                                                vals[nrow+k][j] = table.getValue( i, keyNos[j] );
    283                                        }
    284                                        if( useValLbl ) {
    285                                                // 実際は、上のループの最後の j ( = keyNos.length )
    286                                                vals[nrow+k][keyNos.length] = table.getColumnLabel( valNos[k] );
    287                                        }
    288
    289                                        String val = table.getValue( i, valNos[k] );
    290                                        if( useValRend ) { val = valClmns[k].getRendererValue( val ); }         // Rndererを使用
    291                                        if( viewMarker != null ) {
    292                                                val = viewMarker.getMarkerString( i,valNos[k],val );                    // ViewMarkerを使用
    293                                        }
    294                                        vals[nrow+k][clm] = val;
    295                                }
    296                        }
    297                        for( int i=0; i<vals.length; i++ ) {
    298                                nTable.addColumnValues( vals[i] );
    299                        }
    300                }
    301
    302                return nTable;
    303        }
    304
    305        /**
    306         * 各行のキーとなるキーカラムの値を連結した値を返します。
    307         *
    308         * @param       row             行番号
    309         * @param       clmNo   カラム番号配列(可変長引数)
    310         *
    311         * @return      各行のキーとなるキーカラムの値を連結した値
    312         * @og.rtnNotNull
    313         */
    314        private String getSeparatedValue( final int row, final int... clmNo ) {
    315                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    316                for( int i=0; i<clmNo.length; i++ ) {
    317                        final String val = table.getValue( row, clmNo[i] );
    318                        if( val != null && val.length() > 0 ) {
    319                                if( i > 0 ) {
    320                                        buf.append( "__" );
    321                                }
    322                                buf.append( val );
    323                        }
    324                }
    325                return buf.toString();
    326        }
    327}