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