    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.fukurou.business;
    017
    018import org.opengion.fukurou.model.DataModel;
    019import org.opengion.fukurou.model.NativeType;
    020import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
    021import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
    022
    023import java.util.concurrent.ConcurrentMap;                                                      // 6.4.3.3 (2016/03/04)
    024import java.util.concurrent.ConcurrentHashMap;                                          // 6.4.3.1 (2016/02/12) refactoring
    025import java.util.Arrays;
    026
    027/**
    028 * 業務ロジックを処理するためのテーブルモデルです。
    029 *
    030 * このテーブルモデルでは、オブジェクト生成時に、カラム配列、値配列を元に、内部データを生成し、
    031 * その後は、行の追加や値の変更はできません。
    032 *
    033 * @og.rev 5.1.1.0 (2009/12/01) 新規作成
    034 * @og.group 業務ロジック
    035 *
    036 * @version 5.0
    037 * @author Hiroki Nakamura
    038 * @since JDK1.6,
    039 */
    040public class ArrayTableModel implements DataModel<String> {
    041
    042        private final String[] names;
    043        private final String[][] vals;
    044        private final String[] modTypes;
    045
    046        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
    047        private final ConcurrentMap<Integer,String[]> rtnMap = new ConcurrentHashMap<>() ;      // 6.4.3.3 (2016/03/04) final化で、初期から作成しておきます。
    048
    049        /**
    050         * 引数に名前配列、値配列を指定したコンストラクター
    051         *
    052         * @param       nms     名前配列
    053         * @param       vs      値２重配列
    054         * @throws  IllegalArgumentException 引数の配列が不正な場合
    055         */
    056        public ArrayTableModel( final String[] nms, final String[][] vs ) {
    057                this( nms, vs, null );
    058        }
    059
    060        /**
    061         * 引数に名前配列、値配列、変更区分配列を指定したコンストラクター
    062         *
    063         * @og.rev 5.6.7.0 (2013/07/27) エラーメッセージを判りやすくする。
    064         * @og.rev 5.7.2.3 (2014/01/31) vsのチェック条件を戻す
    065         * @og.rev 5.7.3.1 (2014/02/14) nmsのチェック条件も戻す
    066         *
    067         * @param       nms     名前配列
    068         * @param       vs      値２重配列
    069         * @param       ms      変更区分の配列
    070         * @throws  IllegalArgumentException 引数の配列が不正な場合
    071         */
    072        public ArrayTableModel( final String[] nms, final String[][] vs, final String[] ms ) {
    073                if( nms == null || nms.length == 0 ) {
    074                        final String errMsg = "引数の名前配列に、null は設定できません。";
    075                        throw new IllegalArgumentException( errMsg );
    076                }
    077                // 5.6.7.0 (2013/07/27) エラーメッセージを判りやすくする。
    078                // 5.7.2.3 (2014/01/31) 結果０行でlength=0で通るようなのでvsのエラーチェック条件を戻す。
    079                if( vs == null ) {
    080                        final String errMsg = "引数の値配列に、null は設定できません。";
    081                        throw new IllegalArgumentException( errMsg );
    082                }
    083                // 5.7.3.1 (2014/02/14) 5.7.2.3での戻しでは不十分だったのでこちらも戻す
    084                // 6.0.0.1 (2014/04/25) These nested if statements could be combined
    085                if( vs.length > 0 && ( vs[0] == null || vs[0].length == 0 || nms.length != vs[0].length ) ) {
    086                        final String errMsg = "名前配列と値配列のカラム数が異なります。"    + CR
    087                                                        + "   nms   =" + Arrays.toString( nms   )                       + CR
    088                                                        + "   vs[0] =" + Arrays.toString( vs[0] ) ;
    089                        throw new IllegalArgumentException( errMsg );
    090                }
    091
    092                final int cols = nms.length;
    093                names = new String[cols];
    094                System.arraycopy( nms, 0, names, 0, cols );
    095
    096                final int rows = vs.length;
    097                vals = new String[rows][cols];
    098                for( int i=0; i<rows; i++ ) {
    099                        System.arraycopy( vs[i], 0, vals[i], 0, cols );
    100                }
    101
    102                if( ms != null && ms.length > 0 ) {
    103                        if( vs.length == ms.length ) {
    104                                modTypes = new String[rows];
    105                                System.arraycopy( ms, 0, modTypes, 0, rows );
    106                        }
    107                        else {
    108                                // 5.6.7.0 (2013/07/27) エラーメッセージを判りやすくする。
    109                                final String errMsg = "変更区分を指定する場合、値配列の行数と一致する必要があります。" + CR
    110                                                                        + "   変更区分 行数 =" + ms.length            + CR
    111                                                                        + "   値配列   行数 =" + vs.length ;
    112                                throw new IllegalArgumentException( errMsg );
    113                        }
    114                }
    115                else {
    116                        modTypes = null;
    117                }
    118        }
    119
    120        /**
    121         * rowで指定された行番号(インデックス番号)に行を追加します。
    122         *
    123         * 値配列をセットする場合は、以下の条件を満たす必要があります。
    124         *   １．行番号は、0～(rowCount-1) の範囲
    125         *   ２．値配列は、not null、 かつ １件以上
    126         *   ３．値配列の個数は、内部カラム数と同じ
    127         *
    128         * ここで登録した値は、内部の値配列と別管理されますので、セット後に、再びゲットしても
    129         * ここでセットした値を取り出すことはできません。
    130         * また、同じ行番号でセットした場合は、後でセットした値が有効です。
    131         *
    132         * ※ ここでの更新時に、modifyType は、設定されません。
    133         * 必要であれば、setModifyType メソッドで個別に設定してください。
    134         *
    135         * ※ インデックス(row)とは、このArrayTableModel に持つ vals 配列の行のインデックスです。
    136         * よって、オリジナルのDBTableModelの行番号ではありません。
    137         *
    138         * @og.rev 5.6.0.3 (2012/01/24) 変更された値を、書き戻す機能を追加します。
    139         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    140         *
    141         * @param   inVals  配列値
    142         * @param   rowNo   追加するインデックス
    143         * @throws      IllegalArgumentException 引数が１，２，３の条件を満たさない場合。
    144         */
    145        @Override       // DataModel
    146        public void setValues( final String[] inVals, final int rowNo ) {
    147                if( rowNo < 0 || rowNo > getRowCount() ) {
    148                        final String errMsg = "引数のインデックスは、0～" + (getRowCount()-1) + " の間で指定してください。index=[" + rowNo + "]";
    149                        throw new IllegalArgumentException( errMsg );
    150                }
    151                else if( inVals == null || inVals.length == 0 ) {
    152                        final String errMsg = "引数の値配列に、null、または 0件配列は指定できません。index=[" + rowNo + "]";
    153                        throw new IllegalArgumentException( errMsg );
    154                }
    155                else if( inVals.length != names.length ) {
    156                        final String errMsg = "引数の値配列の個数と、内部カラム数が一致しません。"
    157                                                        + " index=[" + rowNo + "] : 引数個数=[" + inVals.length + "] != 内部カラム数=[" + names.length + "]";
    158                        throw new IllegalArgumentException( errMsg );
    159                }
    160
    161                // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    162
    163                final int cols = names.length;
    164                final String[] newVals = new String[cols];
    165                System.arraycopy( inVals, 0, newVals, 0, cols );
    166
    167                // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。初期化処理の場所も移動。
    168                rtnMap.put( Integer.valueOf( rowNo ) , newVals );
    169        }
    170
    171        /**
    172         * BizLogicで、データが変更された場合は、このMapで値の配列を返します。
    173         * Mapのキーは、インデックス(row)のIntegerオブジェクトです。値は、設定された String配列です。
    174         * なにも変更がされていなければ、空のConcurrentMapを返しましす。
    175         *
    176         * ※ インデックス(row)とは、このArrayTableModel に持つ vals 配列の行のインデックスです。
    177         * よって、オリジナルのDBTableModelの行番号ではありません。
    178         *
    179         * @og.rev 5.6.0.3 (2012/01/24) 変更された値を、書き戻すためのMap&lt;インデックス,値配列&gt; を返します。
    180         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    181         * @og.rev 6.4.3.3 (2016/03/04) 変更が無い場合は、nulllではなく、空のConcurrentMapを返しましす。
    182         *
    183         * @return      書き戻すためのMap＜インデックス,値配列＞
    184         * @see AbstractBizLogic#isRequireTable()
    185         * @og.rtnNotNull
    186         */
    187        public ConcurrentMap<Integer,String[]> getModifyVals() {
    188                return rtnMap;
    189        }
    190
    191        /**
    192         * カラム名に対応する カラム番号を返します。
    193         *
    194         * 特殊なカラムが指定された場合は、負の値を返します。
    195         * 例えば、[KEY.カラム名]、[I]、[ROW.ID] など、特定の負の値を返します。
    196         * また、カラム名が元のデータモデルに存在しない場合も、負の値か、
    197         * Exception を返します。負の値なのか、Exception なのかは、
    198         * 実装に依存します。
    199         *
    200         * @param       columnName      値が参照されるカラム名
    201         *
    202         * @return  指定されたセルのカラム番号。存在しなければ、-1
    203         * @throws  IllegalArgumentException 引数のカラム名が null の場合
    204         */
    205        @Override       // DataModel
    206        public int getColumnNo( final String columnName ) {
    207                if( columnName == null ) {
    208                        final String errMsg = "引数のカラム名に、null は設定できません。";
    209                        throw new IllegalArgumentException( errMsg );
    210                }
    211
    212                int address = -1;
    213                for( int i=0; i<names.length; i++ ) {
    214                        if( columnName.equalsIgnoreCase( names[i] ) ) {
    215                                address = i;
    216                                break;
    217                        }
    218                }
    219
    220                return address;
    221        }
    222
    223        /**
    224         * カラム名配列に対応する カラム番号配列を返します。
    225         *
    226         * これは、#getColumnNo( String ) に対する 複数のカラム名を検索した
    227         * 場合と同じです。
    228         *
    229         * @og.rev 6.8.6.0 (2018/01/19) 可変長引数から、通常配列に変更。
    230         *
    231         * @param       clmNms  値が参照されるカラム名配列(可変長引数)
    232         *
    233         * @return  指定されたセルのカラム番号配列。
    234         * @og.rtnNotNull
    235         */
    236//      public int[] getColumnNos( final String... clmNms ) {
    237        public int[] getColumnNos( final String[] clmNms ) {
    238                if( clmNms == null || clmNms.length == 0 ) { return new int[0]; }       // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
    239
    240                int[] clmNos = new int[clmNms.length];
    241                for( int j=0; j<clmNms.length; j++ ) {
    242                        int address = -1;
    243                        for( int i=0; i<names.length; i++ ) {
    244                                if( clmNms[j].equalsIgnoreCase( names[i] ) ) {
    245                                        address = i;
    246                                        break;
    247                                }
    248                        }
    249                        clmNos[j] = address;
    250                }
    251
    252                return clmNos;
    253        }
    254
    255        /**
    256         * カラム名配列を返します。
    257         *
    258         * @return      カラム名配列
    259         * @og.rtnNotNull
    260         */
    261        @Override       // DataModel
    262        public String[] getNames() {
    263                return names.clone();
    264        }
    265
    266        /**
    267         * 指定のカラム名引数に相当するデータを２重配列で返します。
    268         *
    269         * @og.rev 6.8.5.0 (2018/01/09) 新規追加
    270         *
    271         * @param       clmNms  値が参照されるカラム名配列(可変長引数)
    272         *
    273         * @return  指定された名引数に相当するデータの２重配列
    274         * @og.rtnNotNull
    275         */
    276        protected String[][] getValues( final String... clmNms ) {
    277                final int[] clmNos = getColumnNos( clmNms );
    278
    279                final String[][] rtns = new String[vals.length][clmNos.length];
    280
    281                for( int row=0; row<vals.length; row++ ) {
    282                        for( int j=0; j<clmNos.length; j++ ) {
    283                                final int col = clmNos[j];
    284                                rtns[row][col] = vals[row][col];
    285                        }
    286                }
    287
    288                return rtns;
    289        }
    290
    291        /**
    292         * row にあるセルの属性値を配列で返します。
    293         *
    294         * @param   row     値が参照される行
    295         *
    296         * @return  指定されたセルの属性値配列
    297         * @og.rtnNotNull
    298         */
    299        @Override       // DataModel
    300        public String[] getValues( final int row ) {
    301                return vals[row].clone();
    302        }
    303
    304        /**
    305         * row および clm にあるセルの属性値をStringに変換して返します。
    306         *
    307         * @param   row     値が参照される行
    308         * @param   clm     値が参照される列
    309         *
    310         * @return  指定されたセルの値
    311         */
    312        @Override       // DataModel
    313        public String getValue( final int row, final int clm ) {
    314                return vals[row][clm];
    315        }
    316
    317        /**
    318         * row および clm にあるセルの属性値をStringに変換して返します。
    319         *
    320         * @param   row     値が参照される行
    321         * @param   clm     値が参照される列(キー)
    322         *
    323         * @return  指定されたセルの値
    324         *
    325         */
    326        public String getValue( final int row, final String clm ) {
    327                return vals[row][getColumnNo( clm )];
    328        }
    329
    330        /**
    331         * データテーブル内の行の数を返します。
    332         *
    333         * @return  モデルの行数
    334         *
    335         */
    336        @Override       // DataModel
    337        public int getRowCount() {
    338                return vals.length;
    339        }
    340
    341        /**
    342         * row 単位に変更されたタイプ(追加/変更/削除)を返します。
    343         * タイプは始めに一度登録するとそれ以降に変更はかかりません。
    344         * つまり、始めに 追加で作成したデータは、その後変更があっても追加のままです。
    345         * なにも変更されていない場合は, ""(ゼロストリング)を返します。
    346         *
    347         * @param   row     値が参照される行
    348         *
    349         * @return  変更されたタイプの値
    350         */
    351        @Override       // DataModel
    352        public String getModifyType( final int row ) {
    353                return modTypes == null ? "" : modTypes[row];
    354        }
    355
    356        /**
    357         * row 単位に変更タイプ(追加/変更/削除)をセットします。
    358         * このメソッドでは、データのバックアップは取りません。
    359         * タイプは始めに一度登録するとそれ以降に変更はかかりません。
    360         * なにも変更されていない場合は, ""(ゼロストリング)の状態です。
    361         *
    362         * @og.rev 6.7.9.1 (2017/05/19) インターフェースの見直しにより、追加
    363         *
    364         * @param   row      値が参照される行
    365         * @param   modType  変更タイプ(追加/変更/削除)
    366         *
    367         */
    368        @Override       // DataModel
    369        public void setModifyType( final int row , final String modType ) {
    370                if( modTypes[row].isEmpty() ) { modTypes[row] = modType; }
    371        }
    372
    373        /**
    374         * clm のNativeタイプを返します。
    375         * Nativeタイプはorg.opengion.fukurou.model.NativeTypeで定義されています。
    376         *
    377         * @og.rev 5.1.8.0 (2010/07/01) NativeType#getType(String) のメソッドを使用するように変更。
    378         *
    379         * @param  clm      値が参照される列
    380         *
    381         * @return Nativeタイプ
    382         * @see org.opengion.fukurou.model.NativeType
    383         */
    384        @Override       // DataModel
    385        public NativeType getNativeType( final int clm ) {
    386                return NativeType.getType( vals[0][clm] );
    387        }
    388
    389        /**
    390         * このオブジェクトの文字列表記を返します。
    391         * デバッグ用です。
    392         *
    393         * @og.rev 5.6.7.0 (2013/07/27) 新規追加
    394         *
    395         * @return 文字列表現
    396         * @og.rtnNotNull
    397         * @see java.lang.Object#toString()
    398         */
    399        @Override       // Object
    400        public String toString() {
    401                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    402
    403                buf.append( "NAMES=" ).append( Arrays.toString( names ) ).append( CR )
    404                        // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    405                        .append( " COL_LEN=" ).append( names == null ? -1 : names.length ).append( CR )
    406                        .append( " ROW_LEN=" ).append( vals  == null ? -1 : vals.length  ).append( CR ) ;
    407
    408                return buf.toString();
    409        }
    410}