001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.fukurou.db;
017
018import org.opengion.fukurou.util.StringUtil;
019import org.opengion.fukurou.system.OgRuntimeException ;                         // 6.4.2.0 (2016/01/29)
020import org.opengion.fukurou.system.Closer;
021import org.opengion.fukurou.model.Formatter;
022import org.opengion.fukurou.model.ArrayDataModel;
023import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
024import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
025
026import java.sql.Connection;
027import java.sql.PreparedStatement;
028import java.sql.ParameterMetaData;
029import java.sql.SQLException;
030
031import java.util.Arrays;
032
033/**
034 * DBTableModel インターフェースを継承した TableModel の実装クラスです。
035 * sql文を execute( query ) する事により,データベースを検索した結果を
036 * DBTableModel に割り当てます。
037 *
038 * メソッドを宣言しています
039 * DBTableModel インターフェースは,データベースの検索結果(Resultset)をラップする
040 * インターフェースとして使用して下さい。
041 *
042 * @og.rev 5.2.2.0 (2010/11/01) パッケージ移動(hayabusa.db ⇒ fukurou.db)
043 * @og.group DB/Shell制御
044 *
045 * @version  4.0
046 * @author   Kazuhiko Hasegawa
047 * @since    JDK5.0,
048 */
049public class DBSimpleTable {
050
051        private final String[]  names   ;                       // データ配列に対応するカラム配列(names)
052        private String[]        keys            ;                       // 登録に使用するカラムキー配列(keys)
053        private int[]           keysNo          ;                       // 登録に使用するカラムキー配列番号
054        private String          table           ;                       // 登録テーブル名
055        private String          where           ;                       // where 条件式[カラム名]を含む
056        private int[]           whereNo         ;                       // [カラム名]に対応するデータ配列番号
057        private String[]        constrain       ;                       // key に対応した制約条件
058
059        private String          connID          ;                       // 登録に使用するコネクションID
060        private boolean         useWhere        ;                       // where が設定されると true にセットされます。
061
062        private Connection      conn            ;
063        private PreparedStatement pstmt ;
064        private ParameterMetaData pMeta ;                       // 5.1.2.0 (2010/01/01) setObject に、Type を渡す。(PostgreSQL対応)
065        private String          query           ;                       // エラーメッセージ用の変数
066        private int                     execCnt         ;
067        private ApplicationInfo appInfo ;                       // 3.8.7.0 (2006/12/15)
068        private boolean useParamMetaData;                       // 5.1.2.0 (2010/01/01) setObject に、Type を渡す。(PostgreSQL対応)
069
070        /**
071         * データ配列のカラム名称配列を指定してオブジェクトを構築します。
072         *
073         * @param       nm      カラム名称配列
074         * @throws RuntimeException tbl が null の場合
075         */
076        public DBSimpleTable( final String[] nm ) {
077                if( nm == null ) {
078                        final String errMsg = "データ配列のカラム名称に null は設定できません。";
079                        throw new OgRuntimeException( errMsg );
080                }
081
082                names = new String[nm.length];
083                System.arraycopy( nm,0,names,0,names.length );
084        }
085
086        /**
087         * 登録に使用するカラムキー配列(keys)を登録します。
088         *
089         * 引数のkey配列が null の場合は、names と同じカラム名称配列(names)が使用されます。
090         * キー配列(keys)は、一度しか登録できません。また、addConstrain等のメソッド
091         * 呼び出しを先に実行すると、カラム名称配列(names)が設定されてしまう為、
092         * その後にこのメソッドを呼び出すとエラーが発生します。
093         *
094         * @param       key     登録カラム名称配列(可変長引数)
095         * @see         #addConstrain( String ,String )
096         * @throws RuntimeException すでに キー配列(keys)が登録済み/作成済みの場合
097         */
098        public void setKeys( final String... key ) {
099                if( keys != null ) {
100                        final String errMsg = "すでに キー配列(keys)が登録済みです。";
101                        throw new OgRuntimeException( errMsg );
102                }
103
104                if( key != null && key.length > 0 ) {           // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
105                        final int size = key.length;
106                        keys = new String[size];
107                        System.arraycopy( key,0,keys,0,size );
108
109                        constrain = new String[size];
110                        Arrays.fill( constrain,"?" );
111
112                        keysNo = new int[size];
113                        for( int i=0; i<size; i++ ) {
114                                final int address = findAddress( names,keys[i] );
115                                if( address >= 0 ) {
116                                        keysNo[i] = address;
117                                }
118                                else {
119                                        final String errMsg = "指定の key は、カラム配列(names)に存在しません"
120                                                                        + " key[" + i + "]=" + key[i]
121                                                                        + " names=" + StringUtil.array2csv( names ) ;
122                                        throw new OgRuntimeException( errMsg );
123                                }
124                        }
125                }
126        }
127
128        /**
129         * カラム名称配列(names)と同じキー配列(keys)を作成します。
130         *
131         * これは、キー配列(keys) が作成されなかった場合の処理です。
132         * keys が null の場合のみ、処理を実行します。
133         *
134         * @see         #setKeys( String[] )
135         */
136        private void makeKeys() {
137                // キー配列(keys) が未設定の場合は、カラム名称配列(names)が設定されます。
138                if( keys == null ) {
139                        keys = names;
140                        final int size = keys.length;
141
142                        constrain = new String[size];
143                        Arrays.fill( constrain,"?" );
144
145                        keysNo = new int[size];
146                        for( int i=0; i<size; i++ ) {
147                                keysNo[i] = i;
148                        }
149                }
150        }
151
152        /**
153         * Insert/Update/Delete 時の登録するテーブル名。
154         *
155         * @param       tbl     テーブル名
156         * @throws RuntimeException tbl が null の場合
157         */
158        public void setTable( final String tbl ) {
159                if( tbl == null ) {
160                        final String errMsg = "table に null は設定できません。";         // 5.1.8.0 (2010/07/01) errMsg 修正
161                        throw new OgRuntimeException( errMsg );
162                }
163
164                table = tbl;
165        }
166
167        /**
168         * データベースの接続先IDを設定します。
169         *
170         * @param       conn    接続先ID
171         */
172        public void setConnectionID( final String conn ) {
173                connID = conn;
174        }
175
176        /**
177         * アクセスログ取得の為,ApplicationInfoオブジェクトを設定します。
178         *
179         * @og.rev 3.8.7.0 (2006/12/15) 新規追加
180         *
181         * @param   appInfo アプリ情報オブジェクト
182         */
183        public void setApplicationInfo( final ApplicationInfo appInfo ) {
184                this.appInfo = appInfo;
185        }
186
187        /**
188         * Insert/Update/Delete 時の PreparedStatement の引数(クエスチョンマーク)制約。
189         *
190         * 制約条件(val)は、そのまま引数に使用されます。通常、? で表される
191         * パラメータに、文字長を制限する場合、SUBSTRB( ?,1,100 ) という
192         * val 変数を与えます。
193         * また、キー一つに対して、値を複数登録したい場合にも、使用できます。
194         * 例えば、NVAL( ?,? ) のような場合、キー一つに値2つを割り当てます。
195         * 値配列の並び順は、キー配列(keys)に対する(?の個数)に対応します。
196         * 注意:カラム名称配列(names)ではありません。また、先にキー配列(keys)を登録
197         * しておかないと、キー配列登録時にエラーが発生します。
198         * 制約条件は、処理するQUERYに対して適用されますので、
199         * key または、val が null の場合は、RuntimeException を Throwします。
200         *
201         * @param       key     制約をかけるキー
202         * @param       val     制約条件式
203         * @see         #setKeys( String[] )
204         * @throws RuntimeException key または、val が null の場合
205         */
206        public void addConstrain( final String key,final String val ) {
207                if( key == null || val == null ) {
208                        final String errMsg = "key または、val に null は設定できません。"
209                                                        + " key=[" + key + "] , val=[" + val + "]" ;
210                        throw new OgRuntimeException( errMsg );
211                }
212
213                // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。
214                if( keys == null ) { makeKeys(); }
215
216                // 制約条件のアドレスは、カラム名称配列(names)でなく、キー配列(keys)を使用します。
217                final int address = findAddress( keys,key );
218                if( address >= 0 ) {
219                        constrain[address] = val;
220                }
221                else {
222                        final String errMsg = "指定の key は、キー配列(keys)に存在しません"
223                                                        + " key=[" + key + "] , val=[" + val + "]"
224                                                        + " keys=" + StringUtil.array2csv( keys ) ;
225                        throw new OgRuntimeException( errMsg );
226                }
227        }
228
229        /**
230         * Update/Delete 時のキーとなるWHERE 条件のカラム名を設定します。
231         *
232         * 通常の WHERE 句の書き方と同じで、カラム配列(names)に対応する設定値(values)の値を
233         * 割り当てたい箇所に[カラム名] を記述します。文字列の場合、設定値をセットする
234         * ときに、シングルコーテーションを使用しますが、[カラム名]で指定する場合は、
235         * その前後に、(')シングルコーテーションは、不要です。
236         * WHERE条件は、登録に使用するキー配列(keys)に現れない条件で行を特定することがありますので
237         * カラム名称配列(names)を元にカラム名のアドレスを求めます。
238         * [カラム名]は、? に置き換えて、PreparedStatement として、実行される形式に変換されます。
239         * 例:FGJ='1' and CLM=[CLM] and SYSTEM_ID in ([SYSID],'**')
240         *
241         * @og.rev 4.3.4.0 (2008/12/01) キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てる
242         * @og.rev 5.0.2.0 (2009/11/01) バグ修正(keysはデータセットのキーなので、where句のカラムに含まれて入いるわけではない)
243         * @og.rev 6.4.1.2 (2016/01/22) PMD refactoring. where は、null をセットするのではなく、useWhere の設定で判定する。
244         * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。
245         * @og.rev 6.9.5.0 (2018/04/23) カラム名が存在しない場合に、Exception を throw するかどうかを指定可能にする。
246         *
247         * @param  wh WHERE条件のカラム名
248         * @throws RuntimeException [カラム名]がカラム配列(names)に存在しない場合
249         */
250        public void setWhere( final String wh ) {
251
252                // 6.4.1.2 (2016/01/22) PMD refactoring.
253                if( wh == null || wh.isEmpty() ) {
254                        useWhere= false;
255                }
256                else {
257                        // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。
258                        // 5.0.2.0 (2009/11/01)
259//                      final ArrayDataModel data = new ArrayDataModel( names );
260                        final ArrayDataModel data = new ArrayDataModel( names,true );   // 6.9.5.0 (2018/04/23) カラム名が存在しない場合に、Exception を throw する
261                        final Formatter format = new Formatter( data,wh );                              // 6.4.3.4 (2016/03/11)
262                        where   = format.getQueryFormatString();
263                        whereNo = format.getClmNos();
264                        useWhere= true;
265                }
266        }
267
268        /**
269         * データをインサートする場合に使用するSQL文を作成します。
270         *
271         * @og.rev 6.2.3.0 (2015/05/01) CSV形式の作成を、String#join( CharSequence , CharSequence... )を使用。
272         *
273         * @return  インサートSQL
274         * @og.rtnNotNull
275         */
276        private String getInsertSQL() {
277                // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。
278                if( keys == null ) { makeKeys(); }
279
280                // 6.2.3.0 (2015/05/01) CSV形式の作成を、String#join( CharSequence , CharSequence... )を使用。
281                final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
282                        .append( "INSERT INTO " ).append( table )
283                        .append( " ( " )
284                        .append( String.join( "," , keys ) )                    // 6.2.3.0 (2015/05/01)
285                        .append( " ) VALUES ( " )
286                        .append( String.join( "," , constrain ) )               // 6.2.3.0 (2015/05/01)
287                        .append( " )" );
288
289                useWhere = false;
290
291                return sql.toString();
292        }
293
294        /**
295         * データをアップデートする場合に使用するSQL文を作成します。
296         *
297         * @og.rev 6.4.1.2 (2016/01/22) PMD refactoring. where は、null をセットするのではなく、useWhere の設定で判定する。
298         *
299         * @return  アップデートSQL
300         * @og.rtnNotNull
301         */
302        private String getUpdateSQL() {
303                // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。
304                if( keys == null ) { makeKeys(); }
305
306                final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
307                        .append( "UPDATE " ).append( table ).append( " SET " )
308                        .append( keys[0] ).append( " = " ).append( constrain[0] );
309
310                for( int i=1; i<keys.length; i++ ) {
311                        sql.append( " , " )
312                                .append( keys[i] ).append( " = " ).append( constrain[i] );
313                }
314
315                // 6.4.1.2 (2016/01/22) PMD refactoring.
316                if( useWhere ) {
317                        sql.append( " WHERE " ).append( where );
318                }
319
320                return sql.toString();
321        }
322
323        /**
324         * データをデリートする場合に使用するSQL文を作成します。
325         *
326         * @og.rev 5.0.2.0 (2009/11/01) バグ修正(削除時はkeysは必要ない)
327         * @og.rev 6.4.1.2 (2016/01/22) PMD refactoring. where は、null をセットするのではなく、useWhere の設定で判定する。
328         *
329         * @return  デリートSQL
330         * @og.rtnNotNull
331         */
332        private String getDeleteSQL() {
333                // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。
334                // 5.0.2.0 (2009/11/01)
335                keys = new String[0];
336
337                final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
338                        .append( "DELETE FROM " ).append( table );
339
340                // 6.4.1.2 (2016/01/22) PMD refactoring.
341                if( useWhere ) {
342                        sql.append( " WHERE " ).append( where );
343                }
344
345                return sql.toString();
346        }
347
348        /**
349         * Insert 処理の開始を宣言します。
350         * 内部的に、コネクションを接続して、PreparedStatementオブジェクトを作成します。
351         * このメソッドと、close() メソッドは必ずセットで処理してください。
352         *
353         * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
354         * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
355         * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)
356         *
357         * @throws SQLException Connection のオープンに失敗した場合
358         */
359        public void startInsert() throws SQLException {
360                execCnt = 0;
361                query = getInsertSQL();
362                conn  = ConnectionFactory.connection( connID,appInfo );
363                pstmt = conn.prepareStatement( query );
364                // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
365                useParamMetaData = ConnectionFactory.useParameterMetaData( connID );    // 5.3.8.0 (2011/08/01)
366                if( useParamMetaData ) {
367                        pMeta = pstmt.getParameterMetaData();
368                }
369        }
370
371        /**
372         * Update 処理の開始を宣言します。
373         * 内部的に、コネクションを接続して、PreparedStatementオブジェクトを作成します。
374         * このメソッドと、close() メソッドは必ずセットで処理してください。
375         *
376         * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
377         * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
378         * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)
379         *
380         * @throws SQLException Connection のオープンに失敗した場合
381         */
382        public void startUpdate() throws SQLException {
383                execCnt = 0;
384                query = getUpdateSQL();
385                conn  = ConnectionFactory.connection( connID,appInfo );
386                pstmt = conn.prepareStatement( query );
387                // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
388                useParamMetaData = ConnectionFactory.useParameterMetaData( connID );    // 5.3.8.0 (2011/08/01)
389                if( useParamMetaData ) {
390                        pMeta = pstmt.getParameterMetaData();
391                }
392        }
393
394        /**
395         * Delete 処理の開始を宣言します。
396         * 内部的に、コネクションを接続して、PreparedStatementオブジェクトを作成します。
397         * このメソッドと、close() メソッドは必ずセットで処理してください。
398         *
399         * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
400         * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
401         * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)
402         *
403         * @throws SQLException Connection のオープンに失敗した場合
404         */
405        public void startDelete() throws SQLException {
406                execCnt = 0;
407                query = getDeleteSQL();
408                conn  = ConnectionFactory.connection( connID,appInfo );
409                pstmt = conn.prepareStatement( query );
410                // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
411                useParamMetaData = ConnectionFactory.useParameterMetaData( connID );    // 5.3.8.0 (2011/08/01)
412                if( useParamMetaData ) {
413                        pMeta = pstmt.getParameterMetaData();
414                }
415        }
416
417        /**
418         * データ配列を渡して実際のDB処理を実行します。
419         *
420         * この処理の前に、startXXXX をコールしておき、INSER,UPDATE,DELETEのどの
421         * 処理を行うか、宣言しておく必要があります。
422         * 戻り値は、この処理での処理件数です。
423         * 最終件数は、close( boolean ) 時に取得します。
424         *
425         * @og.rev 4.0.0.0 (2007/11/28) SQLException をきちんと伝播させます。
426         * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
427         * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData 時の setNull 対応(PostgreSQL対応)
428         *
429         * @param       values  カラム配列(names)に対応する設定値配列(可変長引数)
430         *
431         * @return      ここでの処理件数
432         *
433         * @see    #close( boolean )
434         * @throws SQLException Connection のクロースに失敗した場合
435         * @throws RuntimeException Connection DB処理の実行に失敗した場合
436         */
437        public int execute( final String... values ) throws SQLException {
438                final int cnt;
439                try {
440                        int clmNo = 1;  // JDBC のカラム番号は、1から始まる。
441
442                        // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
443                        if( useParamMetaData ) {
444                                // keys に値を割り当てます。
445                                for( int i=0; i<keys.length; i++ ) {
446                                        final int type = pMeta.getParameterType( clmNo );
447                                        // 5.3.8.0 (2011/08/01) setNull 対応
448                                        final String val = values[keysNo[i]];
449                                        if( val == null || val.isEmpty() ) {
450                                                pstmt.setNull( clmNo++, type );
451                                        }
452                                        else {
453                                                pstmt.setObject( clmNo++,val,type );
454                                        }
455                                }
456
457                                // where 条件を使用する場合は、値を割り当てます。
458                                if( useWhere ) {
459                                        for( int i=0; i<whereNo.length; i++ ) {
460                                                final int type = pMeta.getParameterType( clmNo );
461                                                // 5.3.8.0 (2011/08/01) setNull 対応
462                                                final String val = values[whereNo[i]];
463                                                if( val == null || val.isEmpty() ) {
464                                                        pstmt.setNull( clmNo++, type );
465                                                }
466                                                else {
467                                                        pstmt.setObject( clmNo++,val,type );
468                                                }
469                                        }
470                                }
471                        }
472                        else {
473                                // keys に値を割り当てます。
474                                for( int i=0; i<keys.length; i++ ) {
475                                        pstmt.setObject( clmNo++,values[keysNo[i]] );
476                                }
477
478                                // where 条件を使用する場合は、値を割り当てます。
479                                if( useWhere ) {
480                                        for( int i=0; i<whereNo.length; i++ ) {
481                                                pstmt.setObject( clmNo++,values[whereNo[i]] );
482                                        }
483                                }
484                        }
485
486                        cnt = pstmt.executeUpdate();
487                        execCnt += cnt;
488                }
489                catch( final SQLException ex) {
490                        Closer.stmtClose( pstmt );
491                        pMeta = null;           // 5.1.2.0 (2010/01/01)
492                        if( conn != null ) {
493                                conn.rollback();
494                                ConnectionFactory.remove( conn,connID );
495                                conn = null;
496                        }
497                        final String errMsg = "DB処理の実行に失敗しました。" + CR
498                                                        + " query=[" + query + "]" + CR
499                                                        + " values=" + StringUtil.array2csv( values );
500                        throw new OgRuntimeException( errMsg ,ex );
501                }
502                return cnt;
503        }
504
505        /**
506         * DB処理をクロースします。
507         *
508         * 引数には、commit させる場合は、true を、rollback させる場合は、false をセットします。
509         * 戻り値は、今まで処理された合計データ件数です。
510         * この処理は、SQLException を内部で RuntimeException に変換している為、catch 節は
511         * 不要ですが、必ず finally 節で呼び出してください。そうしないと、リソースリークの
512         * 原因になります。
513         *
514         * @og.rev 5.1.2.0 (2010/01/01) pMeta のクリア
515         *
516         * @param  commitFlag コミットフラグ [true:commitする/false:rollbacする]
517         *
518         * @return      今までの合計処理件数
519         */
520        public int close( final boolean commitFlag ) {
521                if( conn != null ) {
522                        try {
523                                if( commitFlag ) {      conn.commit();  }
524                                else {                          conn.rollback(); }
525                        }
526                        catch( final SQLException ex) {
527                                ConnectionFactory.remove( conn,connID );
528                                conn = null;
529                                final String errMsg = "DB処理を確定(COMMIT)できませんでした。" + CR
530                                                                + " query=[" + query + "]" + CR ;
531                                throw new OgRuntimeException( errMsg,ex );
532                        }
533                        finally {
534                                Closer.stmtClose( pstmt );
535                                pMeta = null;           // 5.1.2.0 (2010/01/01)
536                                ConnectionFactory.close( conn,connID );
537                                conn = null;
538                        }
539                }
540
541                return execCnt;
542        }
543
544        /**
545         * 文字列配列中の値とマッチするアドレスを検索します。
546         * 文字列配列がソートされていない為、バイナリサーチが使えません。よって、
547         * 総当りでループ検索しています。
548         * 総数が多い場合は、遅くなる為、マップにセットして使用することを検討ください。
549         *
550         * @param       data    ターゲットの文字列配列中
551         * @param       key     検索する文字列
552         *
553         * @return  ターゲットの添え字(存在しない場合は、-1)
554         */
555        private int findAddress( final String[] data,final String key ) {
556                int address = -1;
557                if( data != null && key != null ) {
558                        for( int i=0; i<data.length; i++ ) {
559                                if( key.equalsIgnoreCase( data[i] ) ) {
560                                        address = i;
561                                        break;
562                                }
563                        }
564                }
565                return address;
566        }
567}