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 * ここではテーブルの回転<del>及びその逆回転</del>を行います。
034 *   8.0.0.0 (2021/07/31) 逆回転 廃止
035 *
036 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
037 * 【パラメータ】
038 *  {
039 *       KEY_CLM    : キーカラム(複数指定可)    (必須)
040 *       ROTATE_CLM : 回転するカラム            (必須)
041 *       VALUE_CLM  : 回転カラムの値            (必須)
042 *       USE_LABEL  : 値ラベルのカラムを生成するか (任意指定 初期値:false) 8.0.0.0 (2021/07/31)
043 *       USE_RENDERER: 値の表示に、renndererを使用するか (任意指定 初期値:false) 8.0.0.0 (2021/07/31)
044 *  <del>REVERSE    : 回転(false)・逆回転(true) (任意指定 初期値:false)</del> 8.0.0.0 (2021/07/31) 廃止
045 *       MUST_CLM   : 必須属性を定義するカラム  (任意指定 初期値:false)
046 *       DEF_CLM    : 初期値を定義するカラム    (任意指定)
047 *  }
048 *
049 *  ※ それぞれに指定されたカラム名が存在しない場合は、処理されませんのでご注意下さい。
050 *
051 * ①回転
052 *  キーカラムに指定された値が同じN行を1行として回転します。
053 *  (キーカラムの値がブレイクしたタイミングで、行を変更します)
054 *  このN行に含まれる回転カラムの値がカラム名に、回転カラム値が各カラムの値になります。
055 *  キーカラムは、CSV形式で複数指定可能です。
056 *
057 *  生成されたテーブルモデルのカラムは、始めのMカラムがキーカラムに、その後ろのNカラムが
058 *  回転されたカラムになります。
059 *
060 *  また、元テーブルにMUST_CLMにより、各カラムの必須属性を定義することが
061 *  できます。(MUST属性は、'1'又は'true'の場合に必須になります。)
062 *
063 * 8.0.0.0 (2021/07/31)
064 *  USE_LABEL="true" を指定した場合、VALUE_CLM のラベルを、キーカラムと回転カラムの間に
065 *  追加します。これは、VALUE_CLM を 複数指定できる機能追加に伴う処置です。
066 *
067 * 8.0.0.0 (2021/07/31) 逆回転 廃止
068 * <del>②逆回転
069 *  回転時の逆の挙動になります。
070 *  "キーカラムに指定されたカラム以外"を回転カラムで指定されたカラムの値として分解します。
071 *  各回転カラムの値は、回転カラム値に指定されたカラムに格納されます。
072 *
073 *  分解後のカラム数は、キーカラム数 + 2 (回転カラム、回転カラム値)になります。
074 *  また、行数は、(分解前の行数) x (回転カラム数)になります。
075 *</del>
076 *
077 * @og.formSample
078 * ●形式:
079 *      ① &lt;og:tableFilter classId="ROTATE" selectedAll="true"
080 *                   keys="KEY_CLM,ROTATE_CLM,VALUE_CLM" vals='"GOKI,MAX_SID,MAX_TM_RPS",TOKEN,X_VAL' /&gt;
081 *
082 *      ② &lt;og:tableFilter classId="ROTATE"  selectedAll="true" &gt;
083 *               {
084 *                   KEY_CLM    : GOKI,MAX_SID,MAX_TM_RPS ;
085 *                   ROTATE_CLM : TOKEN ;
086 *                   VALUE_CLM  : X_VAL ;
087 *               }
088 *         &lt;/og:tableFilter&gt;
089 *
090 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
091 * @og.rev 8.0.0.0 (2021/07/31) VALUE_CLMの複数指定対応と、USE_LABEL属性の追加
092 *
093 * @version  0.9.0  2000/10/17
094 * @author   Hiroki Nakamura
095 * @since    JDK1.1,
096 */
097public class TableFilter_ROTATE extends AbstractTableFilter {
098        // * このプログラムのVERSION文字列を設定します。 {@value} */
099        private static final String VERSION = "8.0.0.0 (2021/07/31)" ;
100
101        private DBTableModel    table    ;                      // 5.5.2.6 (2012/05/25) 共通に使うため、変数定義
102        private ResourceManager resource ;                      // 5.5.2.6 (2012/05/25) 共通に使うため、変数定義
103
104        /**
105         * デフォルトコンストラクター
106         *
107         * @og.rev 6.4.1.1 (2016/01/16) keysMap を、サブクラスから設定させるように変更。
108         */
109        public TableFilter_ROTATE() {
110                super();
111                initSet( "KEY_CLM"      , "キーカラム(複数指定可)"                                        );
112                initSet( "ROTATE_CLM"   , "回転するカラム"                                                     );
113                initSet( "VALUE_CLM"    , "回転カラムの値(複数指定可)"                              );
114                initSet( "USE_LABEL"    , "値ラベルのカラムを生成するか(初期値:false)"   );      // 8.0.0.0 (2021/07/31)
115                initSet( "USE_RENDERER" , "値の表示にrenndererを使用するか(初期値:false)" );  // 8.0.0.0 (2021/07/31)
116//              initSet( "REVERSE"              , "回転(false)/逆回転(true) (初期値:false)"     );      // 8.0.0.0 (2021/07/31) 廃止
117                initSet( "MUST_CLM"             , "必須属性を定義するカラム (初期値:false)"    );
118                initSet( "DEF_CLM"              , "初期値を定義するカラム"                                 );
119        }
120
121        /**
122         * DBTableModel処理を実行します。
123         *
124         * @og.rev 4.3.7.4 (2009/07/01) 新規追加
125         * @og.rev 5.5.2.6 (2012/05/25) protected変数を、private化したため、getterメソッドで取得するように変更
126         *
127         * @return 処理結果のDBTableModel
128         */
129        public DBTableModel execute() {
130                table    = getDBTableModel();           // 5.5.2.6 (2012/05/25) インターフェースにgetterメソッド追加
131                resource = getResource();                       // 5.5.2.6 (2012/05/25) インターフェースにgetterメソッド追加
132
133//              // 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)
134//              final boolean reverse = StringUtil.nval( getValue( "REVERSE" ), false );
135
136//              //                               逆回転(true)                回転(false)
137//              return reverse ? getRecoverdTable() : getRotateTable();
138                return getRotateTable();
139        }
140
141        /**
142         * 回転後のDBTableModelを返します。
143         *
144         * @og.rev 5.1.8.0 (2010/07/01) メソッド名変更(setDefValue ⇒ setDefault)
145         * @og.rev 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
146         * @og.rev 8.0.0.0 (2021/07/31) VALUE_CLMの複数指定対応と、USE_LABEL属性の追加
147         *
148         * @return 回転後のDBTableModel
149         */
150        private DBTableModel getRotateTable() {
151                // 8.0.0.0 (2021/07/31) VALUE_CLMの複数指定対応と、USE_LABEL属性の追加
152                // エラー時の原因表示を入れておきます。
153
154                final String tmpKeyClm  = getValue( "KEY_CLM" );
155                final String[] keyClm   = StringUtil.csv2Array( tmpKeyClm );
156                if( keyClm.length == 0 ) { System.out.println( "KEY_CLM is none[" + tmpKeyClm + "]" ); }
157
158                final String tmpRotClm  = getValue( "ROTATE_CLM" );
159                final int rotateNo              = table.getColumnNo( tmpRotClm, false );
160                if( rotateNo < 0) { System.out.println( "ROTATE_CLM is none[" + tmpRotClm + "]" ); }
161
162                final String tmpValClm  = getValue( "VALUE_CLM" );
163                final String[] valClm   = StringUtil.csv2Array( tmpValClm );
164                if( valClm.length == 0 ) { System.out.println( "VALUE_CLM is none[" + tmpValClm + "]" ); }
165
166//              final String[] keyClm   = StringUtil.csv2Array( getValue( "KEY_CLM"    ) );
167//              final int rotateNo              = table.getColumnNo(    getValue( "ROTATE_CLM" ), false );
168//              final int valNo                 = table.getColumnNo(    getValue( "VALUE_CLM"  ), false );
169
170//              if( keyClm == null || keyClm.length == 0 || rotateNo < 0 || valNo < 0 ) {
171//                      return table;
172//              }
173
174                int clmCount = 0; // 回転後のカラム数
175                // キーカラムのカラム番号を求め、カラム数としてカウントします。
176                final Map<String, Integer> clmMap = new HashMap<>();
177                final int[] keyNos = new int[keyClm.length];
178                for( int i=0; i<keyNos.length; i++ ) {
179                        keyNos[i] = table.getColumnNo( keyClm[i], false );
180                        if( keyNos[i] < 0 ) {
181                                System.out.println( "KEY_CLM is none[" + keyClm[i] + "]" );
182                                return table;
183                        }
184                        clmMap.put( keyClm[i], clmCount++ );            // 固定カラム
185//                      clmCount++;
186                }
187
188                // 8.0.0.0 (2021/07/31) USE_LABEL属性の追加
189                final boolean useValLbl = StringUtil.nval( getValue( "USE_LABEL" ), false );
190                if( useValLbl ) {
191                        clmMap.put( "LABEL", clmCount++ );                      // ラベルカラム
192//                      clmCount++ ;                                                            // ラベルは都度取得になる。
193                }
194
195                final int valStartNo = clmCount ;                               // 値カラム(回転カラム)の開始アドレス
196
197                // 8.0.0.0 (2021/07/31) USE_RENDERER属性の追加
198                final boolean useValRend = StringUtil.nval( getValue( "USE_RENDERER" ), false );
199
200                // 値カラムのカラム番号を求める。(カラム数としてカウントしない=行が増える)
201                final int[] valNos = new int[valClm.length];
202                final DBColumn[] valClmns = new DBColumn[valClm.length];
203                for( int i=0; i<valNos.length; i++ ) {
204                        valNos[i] = table.getColumnNo( valClm[i], false );
205                        if( valNos[i] < 0 ) {
206                                System.out.println( "VALUE_CLM is none[" + valClm[i] + "]" );
207                                return table;
208                        }
209                        if( useValRend ) {
210                                valClmns[i] = table.getDBColumn( valNos[i] );
211                        }
212                }
213
214                int rowCount = 0; // 回転後の行数(KEY_CLMで指定したユニークになる行数で、実際の行数は、VALUE_CLM 倍になる)
215                // 回転カラムの値から回転後のカラム数を求めます。
216                // また同時に、キーカラムの値のブレイク数により行数を求めます。
217                final Map<String, Integer> rowMap               = new HashMap<>();
218                final Map<String, Boolean> mustMap              = new HashMap<>();
219                final Map<String, String>  defaultMap   = new HashMap<>();
220                final int mustNo = table.getColumnNo( getValue( "MUST_CLM"), false );
221                final int defNo  = table.getColumnNo( getValue( "DEF_CLM" ), false );
222                for( int i=0; i<table.getRowCount(); i++ ) {
223                        final String clmKey = table.getValue( i, rotateNo );
224                        if( clmMap.get( clmKey ) == null ) {
225                                clmMap.put( clmKey, clmCount++ );                       // 回転カラム
226//                              clmCount++;
227                        }
228                        // 必須カラム抜き出し
229                        if( mustNo > -1 && StringUtil.nval( table.getValue( i, mustNo ), false ) ) {
230                                mustMap.put( clmKey, true );
231                        }
232                        // デフォルト値を書き換えるカラムの抜き出し
233                        if( defNo > -1 && table.getValue( i, defNo ) != null && table.getValue( i, defNo ).length() > 0 ) {
234                                defaultMap.put( clmKey, table.getValue( i, defNo ) );
235                        }
236
237                        final String rowKey = getSeparatedValue( i, keyNos );
238                        // 6.0.0.1 (2014/04/25) These nested if statements could be combined
239                        if( rowKey != null && rowKey.length() > 0 && rowMap.get( rowKey ) == null ) {
240                                rowMap.put( rowKey, rowCount++ );
241//                              rowCount++;
242                        }
243                }
244
245                // 回転後のカラム一覧よりDBTableModelを初期化します。
246                // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
247                final String names[] = new String[clmMap.size()];
248                // 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
249                clmMap.forEach( (k,v) -> names[v] = k );
250
251                final DBTableModel nTable = DBTableModelUtil.newDBTable();
252                nTable.init( names.length );
253                for( int i=0; i<names.length; i++ ) {
254                        if( mustMap.get( names[i] ) != null ) {
255                                table.addMustType( i, "must" );
256                        }
257                        DBColumn column = resource.makeDBColumn( names[i] );
258                        // 8.0.0.0 (2021/07/31) オリジナルが数字タイプだと、エラーになる可能性があるため
259                        final DBColumnConfig dbConfig = column.getConfig();                     // 固定カラムより値カラムの方が多いので、処理速度は気にしないことにする。
260                        if( valStartNo <= i ) {                 // 値カラム(回転カラム)
261                                dbConfig.setRenderer( "LABEL" );                                                //
262                        }
263
264                        if( defaultMap.get( names[i] ) != null ) {
265//                              final DBColumnConfig dbConfig = column.getConfig();
266                                dbConfig.setDefault( defaultMap.get( names[i] ) );              // 5.1.8.0 (2010/07/01)
267//                              column = new DBColumn( dbConfig );
268                        }
269                        column = new DBColumn( dbConfig );                                                      // 8.0.0.0 (2021/07/31)
270                        nTable.setDBColumn( i, column );                                                        // 5.1.8.0 (2010/07/01)
271                }
272
273                // 値の一覧を作成し、DBTableModelに値をセットします。
274                if( rowCount > 0 ) {
275                        // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
276//                      final String[][] vals = new String[rowCount][names.length];
277                        final String[][] vals = new String[rowCount*valClm.length][names.length];       // 行数は、valClm.length 倍になる
278                        for( int i=0; i<table.getRowCount(); i++ ) {
279                                final int row = rowMap.get( getSeparatedValue( i, keyNos ) );
280                                final int clm = clmMap.get( table.getValue( i, rotateNo ) );
281
282                                final int nrow = row * valClm.length ;
283                                // 8.0.0.0 (2021/07/31) VALUE_CLM が複数存在した場合の処理
284                                for( int k=0; k<valNos.length; k++ ) {
285                                        for( int j=0; j<keyNos.length; j++ ) {
286                                                vals[nrow+k][j] = table.getValue( i, keyNos[j] );
287                                        }
288                                        if( useValLbl ) {
289                                                // 実際は、上のループの最後の j ( = keyNos.length )
290                                                vals[nrow+k][keyNos.length] = table.getColumnLabel( valNos[k] );
291                                        }
292
293                                        final String val = table.getValue( i, valNos[k] );
294                                        vals[nrow+k][clm] = useValRend  ? valClmns[k].getRendererValue( val )           // Rndererを使用
295                                                                                                        : val ;
296                                }
297
298//                              for( int j=0; j<keyNos.length; j++ ) {
299//                                      vals[row][j] = table.getValue( i, keyNos[j] );
300//                              }
301//                              vals[row][clm] = table.getValue( i, valNo );
302                        }
303                        for( int i=0; i<vals.length; i++ ) {
304                                nTable.addColumnValues( vals[i] );
305                        }
306                }
307
308                return nTable;
309        }
310
311        /**
312         * 各行のキーとなるキーカラムの値を連結した値を返します。
313         *
314         * @param       row             行番号
315         * @param       clmNo   カラム番号配列(可変長引数)
316         *
317         * @return      各行のキーとなるキーカラムの値を連結した値
318         * @og.rtnNotNull
319         */
320        private String getSeparatedValue( final int row, final int... clmNo ) {
321                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
322                for( int i=0; i<clmNo.length; i++ ) {
323                        final String val = table.getValue( row, clmNo[i] );
324                        if( val != null && val.length() > 0 ) {
325                                if( i > 0 ) {
326                                        buf.append( "__" );
327                                }
328                                buf.append( val );
329                        }
330                }
331                return buf.toString();
332        }
333
334//      /**
335//       * 逆回転後のDBTableModelを返します。
336//       *
337//       * @og.rev 8.0.0.0 (2021/07/31) 廃止
338//       *
339//       * @return 逆回転後のDBTableModel
340//       */
341//      private DBTableModel getRecoverdTable() {
342//              final String[] keyClm = StringUtil.csv2Array( getValue( "KEY_CLM" ) );
343//              final String rotateClm = getValue( "ROTATE_CLM" );
344//              final String valClm = getValue( "VALUE_CLM" );
345//
346//              if( keyClm == null || keyClm.length == 0
347//                              || rotateClm == null || rotateClm.isEmpty()
348//                              || valClm    == null || valClm.isEmpty() ) {            // 6.1.0.0 (2014/12/26) refactoring
349//                      return table;
350//              }
351//
352//              // キーカラムのカラム番号を求めます。
353//              int[] keyNos = new int[keyClm.length];
354//              for( int i=0; i<keyNos.length; i++ ) {
355//                      keyNos[i] = table.getColumnNo( keyClm[i], false );
356//                      if( keyNos[i] < 0 ) {
357//                              return table;
358//                      }
359//              }
360//
361//              // キーカラム以外(回転カラム以外)のカラム番号を求めます。
362//              int clmIdx = 0;
363//              // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
364//              final int[] clmNos = new int[table.getColumnCount() - keyNos.length];
365//              for( int i=0; i<table.getColumnCount(); i++ ) {
366//                      boolean isClm = true;
367//                      // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop
368//                      for( final int kno : keyNos ) {
369//                              if( i == kno ) { isClm = false; }
370//                      }
371//                      if( isClm ) {
372//                              clmNos[clmIdx] = i;
373//                              clmIdx++;
374//                      }
375//              }
376//
377//              // テーブルモデルを初期化します。
378//              final DBTableModel nTable = DBTableModelUtil.newDBTable();
379//              nTable.init( keyNos.length + 2 );
380//              for( int i=0; i<keyNos.length; i++ ) {
381//                      nTable.setDBColumn( i, resource.makeDBColumn( keyClm[i] ) );
382//              }
383//              nTable.setDBColumn( keyNos.length, resource.makeDBColumn( rotateClm ) );
384//              nTable.setDBColumn( keyNos.length + 1, resource.makeDBColumn( valClm ) );
385//
386//              // 各行を作成し、DBTableModelに登録します。
387//              for( int i=0; i<table.getRowCount(); i++ ) {
388//                      // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop
389//                      for( final int clm : clmNos ) {
390//                              // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
391//                              final String[] vals = new String[keyNos.length + 2];
392//                              for( int k=0; k<keyNos.length; k++ ) {
393//                                      vals[k] = table.getValue( i, keyNos[k] );
394//                              }
395//                              vals[keyNos.length] = table.getColumnName( clm );
396//                              vals[keyNos.length + 1] = table.getValue( i, clm );
397//                              nTable.addColumnValues( vals );
398//                      }
399//              }
400//
401//              return nTable;
402//      }
403}