    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         * 行毎に書込み許可/不許可を指定する場合は,１カラム目に 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}