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