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