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.hayabusa.db;
017
018import org.opengion.fukurou.util.StringUtil;
019import org.opengion.fukurou.model.NativeType;
020import org.opengion.hayabusa.common.HybsSystemException;
021import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
022
023import java.util.List;
024import java.util.ArrayList;
025import java.util.concurrent.ConcurrentMap;                                                      // 6.4.3.3 (2016/03/04)
026import java.util.concurrent.ConcurrentHashMap;                                          // 6.4.3.1 (2016/02/12) refactoring
027import java.util.Set;
028import java.util.HashSet;
029import java.util.Arrays;
030import java.util.Locale ;
031
032/**
033 * DBTableModel インターフェースを継承した TableModel の実装クラスです。
034 * sql文を execute( query ) する事により,データベースを検索した結果を
035 * DBTableModel に割り当てます。
036 *
037 * メソッドを宣言しています
038 * DBTableModel インターフェースは,データベースの検索結果(Resultset)をラップする
039 * インターフェースとして使用して下さい。
040 *
041 * @og.group テーブル管理
042 *
043 * @version  4.0
044 * @author   Kazuhiko Hasegawa
045 * @since    JDK5.0,
046 */
047public class DBTableModelImpl implements DBTableModel {
048        /** カラムオブジェクト配列 */
049        protected       DBColumn[]                      dbColumns       ;
050        /** カラム名称配列 */
051        protected       String[]                        names           ;
052        /** テータリスト */
053        protected       List<String[]>          data            ;
054        /** 行ヘッダー情報 */
055        protected       List<DBRowHeader>       rowHeader       ;
056        /**
057         * カラムアドレスマップ情報
058         * 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
059        */
060        protected       ConcurrentMap<String,Integer>   columnMap       ;
061        /** オーバーフローフラグ */
062        protected       boolean                         overflow        ;
063
064        /** カラム数 */
065        protected   int                 numberOfColumns         ;
066
067        // 3.5.5.5 (2004/04/23) 整合性キー(オブジェクトの作成時刻)追加
068        /** 整合性キー(オブジェクトの作成時刻) */
069        protected       String          consistencyKey  = String.valueOf( System.currentTimeMillis() );
070        private         String[]        lastData                ;
071        private         int             lastRow                 = -1;
072
073        // 4.1.2.1 (2008/03/13) カラム(列)にmustタイプ値を割り当てます。
074        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
075        private final   ConcurrentMap<String,Set<String>> mustMap = new ConcurrentHashMap<>() ; // 4.3.1.1 (2008/08/23) final化
076
077        /**
078         * デフォルトコンストラクター
079         *
080         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
081         */
082        public DBTableModelImpl() { super(); }          // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
083
084        /**
085         * このオブジェクトを初期化します。
086         * 指定の引数分の内部配列を作成します。
087         *
088         * @og.rev 3.1.0.0 (2003/03/20) 実装を、Vector ,Hashtable から、ArrayList ,HashMapに、変更。
089         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
090         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
091         *
092         * @param   columnCount カラム数
093         */
094        @Override       // DBTableModel
095        public void init( final int columnCount ) {
096                data                    = new ArrayList<>( BUFFER_MIDDLE );
097                rowHeader               = new ArrayList<>( BUFFER_MIDDLE );
098                names                   = new String[columnCount];
099                dbColumns               = new DBColumn[ columnCount ];
100                numberOfColumns = columnCount;
101                columnMap               = new ConcurrentHashMap<>();    // 6.4.3.1 (2016/02/12)
102                lastRow                 = -1;                                                   // 3.5.5.7 (2004/05/10)
103        }
104
105        /**
106         * このオブジェクトをヘッダー部分をコピーし、データを初期化します。
107         * これは、カラムなどヘッダー系の情報は、元と同じオブジェクトを共有し、
108         * データ部のみ空にした DBTableModel を作成することを意味します。
109         * この際、consistencyKey も複写しますので、整合性は崩れないように、
110         * データ登録を行う必要があります。
111         *
112         * @og.rev 4.0.0.0 (2007/06/28) 新規作成
113         *
114         * @return  DBTableModelオブジェクト
115         */
116        @Override       // DBTableModel
117        public DBTableModel newModel() {
118                final DBTableModelImpl table = new DBTableModelImpl();
119
120                table.data                              = new ArrayList<>( BUFFER_MIDDLE );
121                table.rowHeader                 = new ArrayList<>( BUFFER_MIDDLE );
122                table.names                             = names;
123                table.dbColumns                 = dbColumns;
124                table.numberOfColumns   = numberOfColumns;
125                table.columnMap                 = columnMap;
126                table.lastRow                   = -1;
127                table.consistencyKey    = consistencyKey;
128
129                return table ;
130        }
131
132        /**
133         * カラム名配列を返します。
134         *
135         * @og.rev 3.0.0.0 (2002/12/25) カラム名配列を取得するメソッドを追加する。
136         * @og.rev 3.5.6.0 (2004/06/18) 配列をそのまま返さずに、clone して返します。
137         * @og.rev 3.6.0.0 (2004/09/22) names が null の場合は、初期設定エラーとします。
138         *
139         * @return      カラム名配列
140         * @og.rtnNotNull
141         */
142        @Override       // DataModel
143        public String[] getNames() {
144                if( names != null ) {
145                        return names.clone();
146                }
147
148                final String errMsg = "カラム名配列が、初期化されていません。";
149                throw new HybsSystemException( errMsg );
150        }
151
152        //////////////////////////////////////////////////////////////////////////
153        //
154        //   DBTableModelImpl 独自の実装部分
155        //
156        //////////////////////////////////////////////////////////////////////////
157
158        /**
159         * column に対応した 値を登録します。
160         * column には、番号ではなく、ラベルを指定します。
161         * 指定の行番号が、内部のデータ件数より多い場合は、データを追加します。
162         *
163         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
164         *
165         * @param   aRow    値が変更される行
166         * @param   columnName    値が変更されるカラム名
167         * @param   value   新しい値。null も可
168         */
169        public void setValue( final int aRow, final String columnName, final String value ) {
170                final int aColumn = getColumnNo( columnName );
171                final int size = getRowCount();
172                if( size > aRow ) {
173                        setRowHeader( aRow,UPDATE_TYPE );
174                        setValueAt( value , aRow, aColumn );
175                }
176                else {
177                        for( int i=0; i< (aRow-size)+1; i++ ) {
178                                final String[] columnValues = new String[numberOfColumns];
179                                Arrays.fill( columnValues,"" );                                 // 6.1.0.0 (2014/12/26) refactoring
180                                addColumnValues( columnValues );
181                        }
182                        setValueAt( value , aRow, aColumn );
183                }
184        }
185
186        /**
187         * 行を削除します。
188         * 物理削除ではなく、論理削除です。
189         * データを取り込むことは可能です。
190         *
191         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
192         *
193         * @param   aRow    論理削除される行
194         */
195        public void rowDelete( final int aRow ) {
196                setRowHeader( aRow,DELETE_TYPE );
197        }
198
199        /**
200         * row にあるセルのオブジェクト値を置き換えて、行を削除します。
201         * 物理削除ではなく、論理削除です。
202         * 値を置き換えたデータを取り込むことが可能です。
203         *
204         * @og.rev 3.5.4.2 (2003/12/15) 新規追加
205         *
206         * @param   values  新しい配列値。
207         * @param   aRow    論理削除される行
208         *
209         */
210        public void rowDelete( final String[] values, final int aRow ) {
211                if( numberOfColumns == values.length ) {                // 3.5.5.7 (2004/05/10)
212                        setRowHeader( aRow,DELETE_TYPE );
213                        data.set( aRow,values );
214                        lastRow = -1;                           // 3.5.5.7 (2004/05/10)
215                }
216                else {
217                        final String errMsg = "カラム名の個数が不一致です。 [" + numberOfColumns + "] : [" + values.length + "]"
218                                                                + " values=" + StringUtil.array2csv( values ) ;         // 5.1.8.0 (2010/07/01) errMsg 修正
219                        throw new HybsSystemException( errMsg );
220                }
221        }
222
223        /**
224         * 行を物理削除します。
225         * メモリ上で編集する場合に使用しますが,一般アプリケーションからの
226         * 使用は、物理削除の為,お勧めいたしません。
227         *
228         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
229         *
230         * @param   aRow    物理削除される行
231         *
232         */
233        public void removeValue( final int aRow ) {
234                data.remove( aRow );
235                rowHeader.remove( aRow );
236                lastRow = -1;                           // 3.5.5.7 (2004/05/10)
237        }
238
239        //////////////////////////////////////////////////////////////////////////
240        //
241        //   DBTableModel インターフェースの実装部分
242        //
243        //////////////////////////////////////////////////////////////////////////
244
245        /**
246         * カラムのラベル名を返します。
247         * カラムの項目名に対して,見える形の文字列を返します。
248         * 一般には,リソースバンドルと組合せて,各国ロケール毎にラベルを
249         * 切替えます。
250         *
251         * @param   column カラム番号
252         *
253         * @return  カラムのラベル名
254         */
255        @Override       // DBTableModel
256        public String getColumnLabel( final int column ) {
257                return dbColumns[column].getLabel();
258        }
259
260        /**
261         * row および column にあるセルの属性値をStringに変換して返します。
262         *
263         * @og.rev 3.5.5.7 (2004/05/10) 連続同一 row アクセスのキャッシュ利用対応
264         *
265         * @param   aRow     値が参照される行
266         * @param   aColumn  値が参照される列
267         *
268         * @return  指定されたセルの値 String
269         */
270        @Override       // DataModel
271        public String getValue( final int aRow, final int aColumn ) {
272                if( aRow != lastRow ) {
273                        lastData = data.get(aRow);
274                        lastRow = aRow ;
275                }
276                return lastData[aColumn] ;
277        }
278
279        /**
280         * row および columnName にあるセルの属性値をStringに変換して返します。
281         *
282         * @param   aRow       値が参照される行
283         * @param   columnName 値が参照されるカラム名
284         *
285         * @return  指定されたセルの値 String
286         * @see #getValue( int , int )
287         */
288        @Override       // DBTableModel
289        public String getValue( final int aRow, final String columnName ) {
290                return getValue( aRow,getColumnNo( columnName ) );
291        }
292
293        /**
294         * カラム(列)にカラムオブジェクトを割り当てます。
295         * カラムオブジェクトは,ラベルやネームなど,そのカラム情報を
296         * 保持したオブジェクトです。
297         *
298         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
299         *
300         * @param   clm        ヘッダーを適応するカラム(列)
301         * @param   dbColumn   カラムオブジェクト
302         */
303        @Override       // DBTableModel
304        public void setDBColumn( final int clm, final DBColumn dbColumn ) {
305                dbColumns[clm] = dbColumn;
306                names[clm]     = dbColumn.getName();
307                columnMap.put( names[clm].toUpperCase(Locale.JAPAN),Integer.valueOf( clm ) );
308        }
309
310        /**
311         * カラム(列)のカラムオブジェクトを返します。
312         * カラムオブジェクトは,ラベルやネームなど,そのカラム情報を
313         * 保持したオブジェクトです。
314         *
315         * @param       clm     ヘッダーを適応するカラム(列)
316         *
317         * @return      カラムオブジェクト
318         */
319        @Override       // DBTableModel
320        public DBColumn getDBColumn( final int clm ) {
321                return dbColumns[ clm ];
322        }
323
324        /**
325         * カラムオブジェクト配列を返します。
326         * カラムオブジェクトは,ラベルやネームなど,そのカラム情報を
327         * 保持したオブジェクトです。
328         *
329         * @og.rev 4.0.0.0 (2005/12/31) 新規追加
330         *
331         * @return      カラムオブジェクト配列
332         */
333        @Override       // DBTableModel
334        public DBColumn[] getDBColumns() {
335                final int size = dbColumns.length;
336                final DBColumn[] clms = new DBColumn[size];
337                System.arraycopy( dbColumns,0,clms,0,size );
338                return clms;
339        }
340
341        /**
342         * カラム名をもとに、そのカラム番号を返します。
343         * カラム名が存在しない場合は、 HybsSystemException を throw します。
344         *
345         * @param   columnName   カラム名
346         *
347         * @return  カラム番号
348         * @see #getColumnNo( String ,boolean )
349         */
350        @Override       // DataModel
351        public int getColumnNo( final String columnName ) {
352                return getColumnNo( columnName,true );
353        }
354
355        /**
356         * カラム名をもとに、そのカラム番号を返します。
357         * useThrow が、true の場合は、カラム名が存在しない場合は、 HybsSystemException を
358         * throw します。useThrow が、false の場合は、カラム名が存在しない場合は、 -1 を返します。
359         *
360         * @og.rev 4.0.0.0 (2005/12/31) 新規追加
361         *
362         * @param   columnName   カラム名
363         * @param   useThrow     カラム名が存在しない場合に、Exception を throw するかどうか
364         *
365         * @return  カラム番号
366         * @see #getColumnNo( String )
367         */
368        @Override       // DBTableModel
369        public int getColumnNo( final String columnName,final boolean useThrow ) {
370                if( columnName != null ) {
371                        final Integer no = columnMap.get( columnName.toUpperCase(Locale.JAPAN) );
372                        if( no != null ) { return no.intValue() ; }
373                }
374
375                if( useThrow ) {
376                        final String errMsg = "カラム名が存在しません:[" + columnName + "]" ;
377                        throw new HybsSystemException( errMsg );
378                }
379                else {
380                        return -1;
381                }
382        }
383
384        //////////////////////////////////////////////////////////////////////////
385        //
386        //   DBTableModel クラスのオーバーライド部分
387        //
388        //////////////////////////////////////////////////////////////////////////
389
390        /**
391         * row の下に属性値配列を追加登録します。
392         *
393         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
394         *
395         * @param   values  属性値配列
396         * @param   aRow    値が参照される行
397         *
398         */
399        @Override       // DBTableModel
400        public void addValues( final String[] values ,final int aRow ) {
401                addValues( values, aRow, true ); // 4.3.1.0 (2008/09/04)
402        }
403
404        /**
405         * row の下に属性値配列を追加登録します。
406         * isWritableをfalseにした場合、編集不可能な状態で追加されます。
407         *
408         * @og.rev 4.3.1.0 (2008/09/04) interface に新規登録
409         *
410         * @param   values  属性値配列
411         * @param   aRow    値が参照される行
412         * @param   isWritable 編集不可能な状態で追加するか
413         *
414         */
415        @Override       // DBTableModel
416        public void addValues( final String[] values ,final int aRow, final boolean isWritable ) {
417                data.add( aRow,values );
418                lastRow = -1;                           // 3.5.5.7 (2004/05/10)
419
420                final DBRowHeader rowhed = new DBRowHeader();
421                if( isWritable ) {
422                        rowhed.setType( INSERT_TYPE );
423                }
424                else {
425                        rowhed.setWritable( false );
426                        rowhed.setChecked( false );
427                }
428                rowHeader.add( aRow,rowhed );
429        }
430
431        /**
432         * row あるセルの属性値配列を追加登録します。
433         * これは,初期登録時のみに使用します。
434         *
435         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
436         *
437         * @param   values  属性値配列
438         */
439        @Override       // DBTableModel
440        public void addColumnValues( final String[] values ) {
441                data.add( values );
442                lastRow = -1;                           // 3.5.5.7 (2004/05/10)
443                rowHeader.add( new DBRowHeader() );
444        }
445
446        /**
447         * row あるセルの属性値配列を追加登録します。
448         * これは,初期登録時のみに使用します。
449         * このメソッドでは、同時に、変更タイプ と、書込み許可を指定できます。
450         *
451         * @og.rev 6.2.2.0 (2015/03/27) interface に変更タイプ と、書込み許可を追加
452         *
453         * @param   values   属性値配列
454         * @param   modType  変更タイプ(追加/変更/削除)
455         * @param   rw 書込み可能(true)/不可能(false)
456         */
457        @Override       // DBTableModel
458        public void addColumnValues( final String[] values , final String modType , final boolean rw ) {
459                data.add( values );
460                lastRow = -1;                           // 3.5.5.7 (2004/05/10)
461
462                final DBRowHeader rowhed = new DBRowHeader();
463                if( modType != null ) {
464                        rowhed.setType( modType );
465                }
466                rowhed.setWritable( rw );
467
468                rowHeader.add( rowhed );
469        }
470
471        //////////////////////////////////////////////////////////////////////////
472        //
473        //             Implementation of the TableModel Interface
474        //
475        //////////////////////////////////////////////////////////////////////////
476
477        // MetaData
478
479        /**
480         * カラム名を取得します。
481         *
482         * @param   column  最初のカラムは 0、2番目のカラムは 1、などとする。
483         *
484         * @return  カラム名
485         *
486         */
487        public String getColumnName( final int column ) {
488                return names[column];
489        }
490
491        /**
492         * データテーブル内の列の数を返します。
493         *
494         * @return  モデルの列数
495         *
496         */
497        public int getColumnCount() {
498                return numberOfColumns ;
499        }
500
501        /**
502         * データテーブル内の行の数を返します。
503         *
504         * @return  モデルの行数
505         *
506         */
507        @Override       // DataModel
508        public int getRowCount() {
509                return data.size() ;
510        }
511
512        /**
513         * column および row にあるセルのオブジェクト値を設定します。
514         * このメソッドは、行番号の範囲チェックや、列番号のチェックを行いません。
515         * また、登録に際して、更新マーカー(UPDATE_TYPE等)を設定しません。
516         *
517         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
518         * @og.rev 3.5.3.1 (2003/10/31) インターフェースの見直しにより、private 化する。
519         * @og.rev 4.0.0.0 (2007/05/24) インターフェースの見直しにより、public 化する。
520         *
521         * @param   value   新しい値。null も可
522         * @param   aRow    値が変更される行
523         * @param   aColumn 値が変更される列
524         */
525        public void setValueAt( final String value, final int aRow, final int aColumn ) {
526                String[] row = data.get(aRow);
527                row[ aColumn ] = value;
528                data.set( aRow,row );
529                lastRow = -1;                           // 3.5.5.7 (2004/05/10)
530        }
531
532        //////////////////////////////////////////////////////////////////////////
533        //
534        //             DBTableModel 独自追加分
535        //
536        //////////////////////////////////////////////////////////////////////////
537
538        /**
539         * row にあるセルの属性値を配列で返します。
540         *
541         * @param   aRow     値が参照される行
542         *
543         * @return  指定されたセルの属性値
544         *
545         */
546        @Override       // DataModel
547        public String[] getValues( final int aRow ) {
548                return data.get(aRow);
549        }
550
551        /**
552         * row にあるセルのオブジェクト値を置き換えます。
553         *
554         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
555         *
556         * @param   values  新しい配列値。
557         * @param   aRow    値が変更される行
558         *
559         */
560        @Override       // DataModel
561        public void setValues( final String[] values, final int aRow ) {
562                if( numberOfColumns == values.length ) {                // 3.5.5.7 (2004/05/10)
563                        setRowHeader( aRow,UPDATE_TYPE );
564                        data.set( aRow,values );
565                        lastRow = -1;                           // 3.5.5.7 (2004/05/10)
566                }
567                else {
568                        final String errMsg = "カラム名の個数が不一致です。 [" + numberOfColumns + "] : [" + values.length + "]"
569                                                                + " values=" + StringUtil.array2csv( values ) ;         // 5.1.8.0 (2010/07/01) errMsg 修正
570                        throw new HybsSystemException( errMsg );
571                }
572        }
573
574        /**
575         * 変更済みフラグを元に戻します。
576         *
577         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
578         *
579         * 一般には,データベースにテーブルモデルを登録するタイミングで、
580         * 変更済みフラグを元に戻します。
581         *
582         */
583        @Override       // DBTableModel
584        public void resetModify() {
585                final int size = rowHeader.size() ;
586                DBRowHeader row ;
587                for( int i=0; i<size; i++ ) {
588                        row = rowHeader.get( i );
589                        row.clear();
590                }
591        }
592
593        /**
594         * 変更済みフラグを元に戻します。
595         *
596         * 一般には,データベースにテーブルモデルを登録するタイミングで、
597         * 変更済みフラグを元に戻します。
598         *
599         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
600         *
601         * @param   aRow     値が参照される行
602         */
603        @Override       // DBTableModel
604        public void resetModify( final int aRow ) {
605                final DBRowHeader row = rowHeader.get( aRow );
606                row.clear();
607        }
608
609        /**
610         * row 単位に変更されたタイプ(追加/変更/削除)を返します。
611         * タイプは始めに一度登録するとそれ以降に変更はかかりません。
612         * つまり、始めに 追加で作成したデータは、その後変更があっても追加のままです。
613         * なにも変更されていない場合は, ""(ゼロストリング)を返します。
614         *
615         * @param   aRow     値が参照される行
616         *
617         * @return  変更されたタイプの値 String
618         *
619         */
620        @Override       // DataModel
621        public String getModifyType( final int aRow ) {
622                final DBRowHeader row = rowHeader.get( aRow );
623                return row.getType();
624        }
625
626        /**
627         * row 単位に変更タイプ(追加/変更/削除)をセットします。
628         * このメソッドでは、データのバックアップは取りません。
629         * タイプは始めに一度登録するとそれ以降に変更はかかりません。
630         * なにも変更されていない場合は, ""(ゼロストリング)の状態です。
631         *
632         * @param   aRow     値が参照される行
633         * @param   modType  変更タイプ(追加/変更/削除)
634         *
635         */
636        @Override       // DataModel
637        public void setModifyType( final int aRow,final String modType ) {
638                final DBRowHeader rowhed = rowHeader.get( aRow );
639                rowhed.setType( modType );
640        }
641
642        /**
643         * row 単位に変更タイプ(追加/変更/削除)をセットします。
644         * セットすると同時に、データのバックアップを取ります。
645         * タイプは始めに一度登録するとそれ以降に変更はかかりません。
646         * つまり、始めに 追加で作成したデータは、その後変更があっても追加のままです。
647         * なにも変更されていない場合は, ""(ゼロストリング)の状態です。
648         *
649         * @og.rev 3.5.6.0 (2004/06/18) setBackupData 側で 配列をコピーしているため、こちらでは不要。
650         * @og.rev 3.5.6.4 (2004/07/16) protected 化します。
651         *
652         * @param   aRow     値が参照される行
653         * @param   modType  変更タイプ(追加/変更/削除)
654         */
655        protected void setRowHeader( final int aRow,final String modType ) {
656                final DBRowHeader rowhed = rowHeader.get( aRow );
657
658                rowhed.setBackupData( data.get(aRow) );
659                rowhed.setType( modType );
660        }
661
662        /**
663         * 変更データを初期値(元の取り込んだ状態)に戻します。
664         *
665         * 変更タイプ(追加/変更/削除)に応じて、処理されます。
666         * 追加時は、追加された行を削除します。
667         * 変更時は、変更された行を元に戻します。
668         * 削除時は、削除フラグを解除します。
669         * それ以外の場合(変更されていない場合)は、なにもしません。
670         *
671         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
672         * @og.rev 3.5.4.2 (2003/12/15) "DELETE" 時に値を置き換えた場合にUPDATEと同様に戻します。
673         *
674         * @param   aRow    処理を戻す(取り消す)行
675         */
676        public void resetRow( final int aRow ) {
677                final String modType = getModifyType(aRow) ;
678
679                if( modType.equals( INSERT_TYPE ) ) {
680                        data.remove( aRow );
681                        rowHeader.remove( aRow );
682                }
683                else if( modType.equals( UPDATE_TYPE ) ||
684                                 modType.equals( DELETE_TYPE ) ) {
685                        final DBRowHeader row = rowHeader.get( aRow );
686                        final String[] obj = row.getBackupData();
687                        if( obj != null ) { data.set( aRow,obj ); }
688                        row.clear();
689                }
690                lastRow = -1;                           // 3.5.5.7 (2004/05/10)
691        }
692
693        /**
694         * データが更新された行番号の配列を返します。
695         *
696         * これは、変更があったデータの行番号の配列をピックアップします。
697         *
698         * @og.rev 7.4.2.0 (2021/04/30) 変更があったデータのみを処理するかどうか[true/false]を指定します(初期値:false)
699         *
700         * @return   行番号の配列
701         */
702        public int[] getChangeRowNos() {
703                final List<Integer> rows = new ArrayList<>();
704                final int size = data.size() ;
705                for( int aRow=0; aRow<size; aRow++ ) {
706                        final String[] dat = data.get(aRow);
707                        final DBRowHeader head = rowHeader.get( aRow );
708                        final String[] obj = head.getBackupData();
709                        if( obj != null ) {
710                                for( int aCol=0; aCol<numberOfColumns; aCol++ ) {
711                                        if( dat[aCol] != null && obj[aCol] != null  && !dat[aCol].equals( obj[aCol] ) ) {
712                                                rows.add( aRow );
713                                                break;
714                                        }
715                                }
716                        }
717                }
718
719                // List<Integer> を、int[] に変換します。
720                return rows.stream().mapToInt(i->i).toArray();
721        }
722
723        /**
724         * 書込み許可を返します。
725         *
726         * @param   aRow     値が参照される行
727         *
728         * @return  書込み可能(true)/不可能(false)
729         */
730        public boolean isRowWritable( final int aRow ) {
731                final DBRowHeader row = rowHeader.get( aRow );
732                return row.isWritable();
733        }
734
735        /**
736         * 行が書き込み可能かどうかをセットします。
737         * デフォルト/およびなにも設定しない場合は, DEFAULT_WRITABLE が
738         * 与えられています。
739         * これが true の場合は,書込み許可です。(チェックボックスを表示)
740         * false の場合は,書込み不許可(チェックボックスは表示されません。)
741         *
742         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
743         *
744         * @param   aRow     値が参照される行
745         * @param   rw 書込み可能(true)/不可能(false)
746         */
747        @Override       // DBTableModel
748        public void setRowWritable( final int aRow ,final boolean rw ) {
749                final DBRowHeader row = rowHeader.get( aRow );
750                row.setWritable( rw );
751        }
752
753        /**
754         * 書き込み可能な行(rowWritable == true)のチェックボックスに対して
755         * 初期値を 選択済みか、非選択済みかを返します。
756         *
757         * @param   aRow      値が参照される行
758         *
759         * @return      初期値チェックON(true)/チェックOFF(false)
760         */
761        public boolean isRowChecked( final int aRow ) {
762                final DBRowHeader row = rowHeader.get( aRow );
763                return row.isChecked();
764        }
765
766        /**
767         * 書き込み可能な行(rowWritable == true)のチェックボックスに対して
768         * 初期値を 選択済みにするか、非選択済みにするかを指定します。
769         *
770         * @og.rev 3.1.0.0 (2003/03/20) 同期メソッド(synchronized付き)を非同期に変更する。
771         *
772         * @param   aRow      値が参照される行
773         * @param   rw チェックON(true)/チェックOFF(false)
774         */
775        @Override       // DBTableModel
776        public void setRowChecked( final int aRow ,final boolean rw ) {
777                final DBRowHeader row = rowHeader.get( aRow );
778                row.setChecked( rw );
779        }
780
781        /**
782         * 行指定の書込み許可を与えます。
783         * 具体的には,チェックボックスの表示/非表示を指定します。
784         * これが true の場合は,書込み許可です。(チェックボックスを表示)
785         * false の場合は,書込み不許可(チェックボックスは表示されません。)
786         * 行毎に書込み許可/不許可を指定する場合は,1カラム目に writable
787         * カラムを用意して true/false を指定します。
788         * この writable カラムとの論理積により最終的にチェックボックスの
789         * 表示の ON/OFF が決まります。
790         * なにも設定しない場合は, ViewForm.DEFAULT_WRITABLE が設定されます。
791         *
792         * @param   rw 書込み可能(true)/不可能(false)
793         */
794        @Override       // DBTableModel
795        public void setDefaultRowWritable( final boolean rw ) {
796                final int size = rowHeader.size() ;
797                DBRowHeader row ;
798                for( int i=0; i<size; i++ ) {
799                        row = rowHeader.get( i );
800                        row.setWritable( rw );
801                }
802        }
803
804        /**
805         * 書き込み可能な行(rowWritable == true)のチェックボックスに対して
806         * 初期値を 選択済みにするか、非選択済みにするかを指定します。
807         *
808         * @param   rw 選択状態(true)/非選択状態(false)
809         */
810        @Override       // DBTableModel
811        public void setDefaultRowChecked( final boolean rw ) {
812                final int size = rowHeader.size() ;
813                DBRowHeader row ;
814                for( int i=0; i<size; i++ ) {
815                        row = rowHeader.get( i );
816                        row.setChecked( rw );
817                }
818        }
819
820        /**
821         * 検索結果が オーバーフローしたかどうかをチェックします。
822         * Query で検索した場合に、DB_MAX_ROW_COUNT または、Query.setMaxRowCount( int maxRowCount )
823         * で指定された値よりも検索結果が多い場合に、DBTableModel は、先の設定値までの
824         * データを取り込みます。そのときに、オーバーフローフラグを立てておくことで、最大件数を
825         * オーバーしたかどうかを判断します。
826         *
827         * @return   オーバーフロー(true)/正常(false)
828         */
829        public boolean isOverflow() {
830                return overflow;
831        }
832
833        /**
834         * 検索結果が オーバーフローしたかどうかを設定します。
835         * Query で検索した場合に、DB_MAX_ROW_COUNT または、Query.setMaxRowCount( int maxRowCount )
836         * で指定された値よりも検索結果が多い場合に、DBTableModel は、先の設定値までの
837         * データを取り込みます。そのときに、オーバーフローフラグを立てておくことで、最大件数を
838         * オーバーしたかどうかを判断します。
839         *
840         * @param   of オーバーフロー(true)/正常(false)
841         */
842        @Override       // DBTableModel
843        public void setOverflow( final boolean of ) {
844                overflow = of;
845        }
846
847        /**
848         * 検索されたDBTableModelが登録時に同一かどうかを判断する為の 整合性キーを取得します。
849         *
850         * ここでの整合性は、同一セッション(ユーザー)毎にユニークかどうかで対応します。
851         * 分散環境(複数のセッション間)での整合性は、確保できません。
852         * 整合性キー は、オブジェクト作成時刻としますが、将来変更される可能性があります。
853         *
854         * @og.rev 3.5.5.5 (2004/04/23) 新規追加
855         *
856         * @return   整合性キー(オブジェクトの作成時刻)
857         */
858        public String getConsistencyKey() {
859                return consistencyKey;
860        }
861
862        /**
863         * カラムに定義されたDBTypeよりNativeタイプを返します。
864         * Nativeタイプはorg.opengion.fukurou.model.NativeTypeで定義されています。
865         *
866         * @og.rev 4.1.1.2 (2008/02/28) 新規追加
867         *
868         * @param  clm      値が参照される列
869         *
870         * @return Nativeタイプ
871         * @see org.opengion.fukurou.model.NativeType
872         */
873        @Override       // DataModel
874        public NativeType getNativeType( final int clm ) {
875                return dbColumns[clm].getNativeType();
876        }
877
878        /**
879         * カラム(列)にmustタイプ値を割り当てます。
880         * この値は、columnCheck 時の nullCheck や mustAnyCheck の
881         * チェック対象カラムとして認識されます。
882         *
883         * ※ 6.8.1.4 (2017/08/25)
884         *    type に、null を指定できるようにします。その場合は、type="must" を
885         *    削除する動きになります。
886         *    本来なら、delMustType( int ) などのメソッド追加が良いのですが、
887         *    今回は、変更箇所を少ない目にするため、このメソッドのみで対応します。
888         *
889         * @og.rev 4.1.2.1 (2008/03/13) interface に新規登録
890         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
891         * @og.rev 6.8.1.4 (2017/08/25) mustに、false 指定が出来るようにします。
892         * @og.rev 6.9.3.1 (2018/04/02) mustに、clear 指定で、mustMap すべてをクリアします。
893         *
894         * @param   dbColumn  カラムオブジェクト
895         * @param   type      mustタイプ(must,mustAny,false)
896         */
897        @Override       // DBTableModel
898        public void addMustType( final int dbColumn, final String type ) {
899                if( "clear".equalsIgnoreCase( type ) ) {
900                                mustMap.clear();
901                }
902                else if( type != null && names[dbColumn] != null ) {
903                        if( "false".equalsIgnoreCase( type ) ) {
904                                mustMap.remove( "must" );
905                        }
906                        else {
907                                // たぶん、やりすぎ
908                                // Map#computeIfAbsent : 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
909                                mustMap.computeIfAbsent( type , k -> new HashSet<>() ).add( names[dbColumn] );
910                        }
911                }
912        }
913
914        /**
915         * mustType="must"時のカラム名を、文字列配列として返します。
916         * この値は、columnCheck 時の nullCheck のチェック対象カラムとして
917         * 認識されます。
918         * カラム名配列は、ソート済みです。
919         *
920         * @og.rev 4.1.2.1 (2008/03/13) interface に新規登録
921         *
922         * @return  mustType="must"時のカラム名配列(ソート済み)
923         */
924        @Override       // DBTableModel
925        public String[] getMustArray() {
926                String[] rtn = null;
927
928                final Set<String> set = mustMap.get( "must" );
929                if( set != null && ! set.isEmpty() ) {
930                        rtn = set.toArray( new String[set.size()] );
931                        Arrays.sort( rtn );
932                }
933                return rtn ;
934        }
935
936        /**
937         * mustType="mustAny" 他のカラム名を、文字列配列として返します。
938         * この値は、columnCheck 時の mustAnyCheck のチェック対象カラムとして
939         * 認識されます。
940         * カラム名配列は、ソート済みです。
941         *
942         * @og.rev 4.1.2.1 (2008/03/13) interface に新規登録
943         *
944         * @return  mustType="mustAny"時のカラム名配列(ソート済み)
945         */
946        @Override       // DBTableModel
947        public String[] getMustAnyArray() {
948
949                final List<String> list = new ArrayList<>();
950
951                final String[] keys = mustMap.keySet().toArray( new String[mustMap.size()] );
952                for( int i=0; i<keys.length; i++ ) {
953                        final String key = keys[i];
954                        if( ! "must".equals( key ) ) {
955                                final Set<String> set = mustMap.get( key );
956                                if( set != null && !set.isEmpty() ) {
957                                        final String str = StringUtil.iterator2line( set.iterator(),"|" );
958                                        list.add( str );
959                                }
960                        }
961                }
962
963                String[] rtn = null;
964                if( ! list.isEmpty() ) {
965                        rtn = list.toArray( new String[list.size()] );
966                        Arrays.sort( rtn );
967                }
968
969                return rtn ;
970        }
971}