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