    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 *      ① &lt;og:tableFilter classId="UNIQ_NAME" keys="NAME_IN,NAME_OUT" vals="NAME_CLM,RYAKU_CLM" /&gt;
    054 *
    055 *      ② &lt;og:tableFilter classId="UNIQ_NAME" &gt;
    056 *                 {
    057 *                     NAME_IN  : NAME_CLM  ;
    058 *                     NAME_OUT : RYAKU_CLM ;
    059 *                 }
    060 *         &lt;/og:tableFilter&gt;
    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}