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