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