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