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 org.opengion.hayabusa.common.HybsSystemException;
019import org.opengion.hayabusa.db.AbstractTableFilter;
020import org.opengion.hayabusa.db.DBTableModel;
021
022import org.opengion.fukurou.util.ErrorMessage;
023import org.opengion.fukurou.util.StringUtil;
024
025import java.util.Map;
026import java.util.HashMap;
027
028/**
029 * TableFilter_UNIQ_NAME は、TableFilter インターフェースを継承した、DBTableModel 処理用の
030 * 実装クラスです。
031 *
032 * ここでは、NAME_IN,NAME_OUT,GROUP_KEY,TYPE より、名前を最短ユニーク化します。
033 * 例えば、氏名で、姓と名で、同姓の場合、姓(名)を付けることで、区別することができます。
034 *
035 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
036 * 【パラメータ】
037 *  {
038 *       NAME_IN   : NAME_CLM  ;    名前のオリジナルのカラムを指定します。(必須)
039 *       NAME_OUT  : RYAKU_CLM ;    変換後の名前を設定するカラムを指定します。NAME_INと同じでもかまいません。(必須)
040 *       GROUP_KEY : CDBUMON   ;    名前をユニークにするグループを指定するカラム名を指定します。(選択)
041 *                                  グループはソートされている必要があります。内部的にはキーブレイク処理します。
042 *       TYPE      : [1 or 2]  ;    処理の方法を指定します(初期値:1)
043 *                                    1:姓と名を分けます。重複分は、姓(名) 形式で、ユニークになるまで、名の文字を増やします。
044 *                                    2:姓と名を分けます。1. と異なるのは、最初に見つけた重複分は、姓 のまま残します。
045 *  }
046 *
047 * 姓名の分離は、全角または、半角のスペースで区切ります。また、重複しなければ、(名)は付きません。
048 * TYPE="2" の方式は、慣例的に、昔からいる社員は苗字そのままで、後から入社した人にだけ(名)を
049 * 付けたい場合に、名前を入社年の古い順にならべることで、実現できます。
050 *
051 * @og.formSample
052 * ●形式:
053 *      ① <og:tableFilter classId="UNIQ_NAME" keys="NAME_IN,NAME_OUT" vals="NAME_CLM,RYAKU_CLM" />
054 *
055 *      ② <og:tableFilter classId="UNIQ_NAME" >
056 *                 {
057 *                     NAME_IN  : NAME_CLM  ;
058 *                     NAME_OUT : RYAKU_CLM ;
059 *                 }
060 *         </og:tableFilter>
061 *
062 * @og.rev 5.5.0.3 (2012/03/12) 新規作成
063 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
064 *
065 * @version  0.9.0  2000/10/17
066 * @author   Kazuhiko Hasegawa
067 * @since    JDK1.6,
068 */
069public class TableFilter_UNIQ_NAME extends AbstractTableFilter {
070        /** このプログラムのVERSION文字列を設定します。   {@value} */
071        private static final String VERSION = "6.5.0.1 (2016/10/21)" ;
072
073        /**
074         * デフォルトコンストラクター
075         *
076         * @og.rev 6.4.1.1 (2016/01/16) keysMap を、サブクラスから設定させるように変更。
077         */
078        public TableFilter_UNIQ_NAME() {
079                super();
080                initSet( "NAME_IN"      , "名前のオリジナルのカラムを指定(必須)"                                 );
081                initSet( "NAME_OUT"     , "変換後の名前を設定するカラムを指定(必須)"                               );
082                initSet( "GROUP_KEY", "名前をユニークにするグループを指定するカラム名を指定"      );
083                initSet( "TYPE"         , "処理方法を指定(初期値:1) [1 or 2]"                                             );
084        }
085
086        /**
087         * DBTableModel処理を実行します。
088         *
089         * @og.rev 5.5.2.6 (2012/05/25) protected変数を、private化したため、getterメソッドで取得するように変更
090         * @og.rev 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
091         * @og.rev 6.5.0.1 (2016/10/21) ErrorMessage をまとめるのと、直接 Throwable を渡します。
092         *
093         * @return 処理結果のDBTableModel
094         */
095        public DBTableModel execute() {
096                final DBTableModel table = getDBTableModel();           // 5.5.2.6 (2012/05/25) インターフェースにgetterメソッド追加
097
098                final String nameIn   = getValue( "NAME_IN" );
099                final String nameOut  = getValue( "NAME_OUT" );
100
101                final int inClmNo  = table.getColumnNo( nameIn,false );         // 存在しない場合は、-1 を返す。
102                final int outClmNo = table.getColumnNo( nameOut,false );
103
104                // 必須チェック
105                if( inClmNo < 0 || outClmNo <0 ) {
106                        final String errMsg = "TableFilter_UNIQ_NAME では、NAME_IN、NAME_OUT 属性は必須です。"
107                                                + " NAME_IN =" + nameIn
108                                                + " NAME_OUT=" + nameOut ;
109                        throw new HybsSystemException( errMsg );
110                }
111
112                // 名前をユニーク化するためのマップ。キーがユニーク化する名前。値は、行番号
113                final Map<String,Integer> nameMap = new HashMap<>() ;
114
115                String[] data  = null;
116                final int rowCnt = table.getRowCount();
117                String preKey = null;
118                int row = 0;
119
120                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
121                final int type = StringUtil.nval( getValue( "TYPE" ), 1 );      // NULL の場合、初期値:1
122                final String groupKey = getValue( "GROUP_KEY" );
123                final int grpClmNo = table.getColumnNo( groupKey,false );
124                try {
125                        for( row=0; row<rowCnt; row++ ) {
126                                data  = table.getValues( row );
127                                final String orgName = data[inClmNo];           // オリジナルの名称
128
129                                if( grpClmNo >= 0 && preKey == null ) {
130                                        preKey = data[grpClmNo];
131                                        if( preKey == null ) { preKey = ""; }
132                                }
133
134                                if( orgName != null && !orgName.isEmpty() ) {
135                                        final String[] seimei = makeSeiMei( orgName );
136                                        final String sei = seimei[0];
137                                        final String mei = seimei[1];
138
139                                        if( nameMap.containsKey( sei ) ) {      // 存在する場合。つまり重複
140                                                if( type == 1 ) {       // 重複時に最初の分も(名)を付ける。
141                                                        final Integer oldInt = nameMap.get( sei );
142                                                        if( oldInt != null ) {  // null の場合は、先に重複処理済み
143                                                                // オリジナルの姓名を取得
144                                                                final String oldName = table.getValue( oldInt.intValue(),inClmNo );
145                                                                final String[] oldSeimei = makeSeiMei( oldName );
146
147                                                                final String key = makeKey( nameMap , oldSeimei[0] , oldSeimei[1] );
148                                                                nameMap.put( key, oldInt );             // 変更後のキーと値
149                                                                nameMap.put( sei, null );               // 比較用に元のキーは残すが値は残さない。
150                                                        }
151                                                }
152
153                                                final String key = makeKey( nameMap , sei , mei );
154                                                nameMap.put( key, Integer.valueOf( row ) );
155                                        }
156                                        else {
157                                                nameMap.put( sei, Integer.valueOf( row ) );
158                                        }
159                                }
160
161                                // キーブレイクのチェック
162                                if( grpClmNo >= 0 && !preKey.equals( data[grpClmNo] ) ) {
163                                        preKey = data[grpClmNo];
164                                        if( preKey == null ) { preKey = ""; }
165
166                                        // 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
167                                        nameMap.forEach( (key,orow) -> {
168                                                if( orow != null ) { table.setValueAt( key , orow , outClmNo ); }
169                                        } );
170                                        nameMap.clear();
171                                }
172                        }
173                        // 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
174                        nameMap.forEach( (key,orow) -> {
175                                if( orow != null ) { table.setValueAt( key , orow , outClmNo ); }
176                        } );
177                }
178                catch( final RuntimeException ex ) {
179                        // 6.5.0.1 (2016/10/21) ErrorMessage をまとめるのと、直接 Throwable を渡します。
180                        makeErrorMessage( "TableFilter_UNIQ_NAME Error",ErrorMessage.NG )
181                                .addMessage( row+1,ErrorMessage.NG,"UNIQ_NAME"
182                                        , StringUtil.array2csv( data )
183                                        , "NAME_IN=[" + nameIn + "]"
184                                        , "NAME_OUT=[" + nameOut + "]"
185                                        , "GROUP_KEY=[" + groupKey + "]"
186                                        , "TYPE=[" + type + "]"
187                                )
188                                .addMessage( ex );
189                }
190                return table;
191        }
192
193        /**
194         * オリジナルの姓名から、姓と名を分離します。
195         *
196         * @param       orgName オリジナルのフルネーム
197         *
198         * @return 姓と名の分割結果
199         */
200        private String[] makeSeiMei( final String orgName ) {
201                final String[] seimei = new String[2];
202
203                int adrs = orgName.indexOf( ' ' );
204                if( adrs < 0 ) { adrs = orgName.indexOf( ' ' ); }
205                if( adrs < 0 ) {
206                        seimei[0] = orgName.trim();
207                        seimei[1] = "";
208                }
209                else {
210                        seimei[0] = orgName.substring( 0,adrs ).trim();
211                        seimei[1] = orgName.substring( adrs+1 ).trim();
212                }
213
214                return seimei ;
215        }
216
217        /**
218         * マップに存在しないキーを作成します。マップへの登録は、行いません。
219         *
220         * @param       nameMap 過去に登録されている名前キーのマップ
221         * @param       sei             オリジナルの姓
222         * @param       mei             オリジナルの名
223         *
224         * @return      新しく作成されたキー
225         */
226        private String makeKey( final Map<String,Integer> nameMap , final String sei , final String mei ) {
227                String key = null;
228
229                boolean flag = true;    // 未処理フラグ
230                for( int i=1; i<=mei.length(); i++ ) {
231                        key = sei + "(" + mei.substring(0,i) + ")" ;
232                        if( ! nameMap.containsKey( key ) ) {    // 存在しない
233                                flag = false;
234                                break;
235                        }
236                }
237                if( flag ) {
238                        for( int i=1; i<10; i++ ) {
239                                key = sei + mei + "("  + i  + ")" ;
240                                if( ! nameMap.containsKey( key ) ) {    // 存在しない
241                                        break;
242                                }
243                        }
244                }
245
246                return key ;
247        }
248}