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 java.io.IOException;
019import java.io.Reader;
020import java.sql.Clob;
021import java.sql.ResultSet;
022import java.sql.ResultSetMetaData;
023import java.sql.SQLException;
024import java.sql.Types;
025import java.sql.Date;
026import java.sql.Timestamp;
027import java.util.Locale;
028
029import org.opengion.fukurou.util.Closer;
030import org.opengion.fukurou.util.HybsDateUtil;
031import static org.opengion.fukurou.util.HybsConst.CR;                           // 6.1.0.0 (2014/12/26) refactoring
032
033/**
034 * ResultSet のデータ処理をまとめたクラスです。
035 * ここでは、ResultSetMetaData から、カラム数、カラム名(NAME列)、
036 * Type属性を取得し、ResultSet で、値を求める時に、Object型の
037 * 処理を行います。
038 * Object型としては、CLOB、ROWID、TIMESTAMP 型のみ取り扱っています。
039 *
040 * @og.rev 6.0.4.0 (2014/11/28) 新規作成
041 * @og.group DB制御
042 *
043 * @version  6.0
044 * @author   Kazuhiko Hasegawa
045 * @since    JDK6.0,
046 */
047public class ResultSetValue {
048
049//      /** システム依存の改行記号をセットします。4.0.0.0(2007/10/17) */
050//      private static final String CR = System.getProperty( "line.separator" );
051
052        private final ResultSet resultSet ;                     // 内部で管理する ResultSet オブジェクト
053        private final int               clmSize ;                       // カラムサイズ
054        private final String[]  names ;                         // カラム名(ResultSetMetaData#getColumnLabel(int) の toUpperCase)
055        private final int[]             type ;                          // java.sql.Types の定数定義
056        private final boolean   useObj ;                        // オブジェクト型の Type が存在するかどうか。
057
058        private final int[]             size ;                          // カラムサイズ(ResultSetMetaData#getColumnDisplaySize(int))
059        private final boolean[] isWrit ;                        // 書き込み許可(ResultSetMetaData#isWritable(int))
060
061        /**
062         * ResultSet を引数にとるコンストラクタ
063         *
064         * ここで、カラムサイズ、カラム名、java.sql.Types の定数定義 を取得します。
065         *
066         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
067         *
068         * @param       res 内部で管理するResultSetオブジェクト
069         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
070         */
071        public ResultSetValue( final ResultSet res ) throws SQLException {
072                resultSet = res;
073
074                final ResultSetMetaData metaData  = resultSet.getMetaData();
075                clmSize = metaData.getColumnCount();
076                names   = new String[clmSize];
077                type    = new int[clmSize];
078                size    = new int[clmSize];
079                isWrit  = new boolean[clmSize];
080
081                boolean tempUseObj = false;                                             // そもそも、オブジェクト系のカラムがあるかどうか
082                for( int i=0; i<clmSize; i++ ) {
083                        final int tp = metaData.getColumnType( i+1 );
084                        type[i] = tp ;
085                        if( tp == Types.CLOB || tp == Types.ROWID || tp == Types.TIMESTAMP ) { tempUseObj = true; }
086                        names[i]  = metaData.getColumnLabel(i+1).toUpperCase(Locale.JAPAN) ;
087                        size[i]   = metaData.getColumnDisplaySize(i+1) ;
088                        isWrit[i] = metaData.isWritable(i+1) ;
089                }
090                useObj = tempUseObj ;
091        }
092
093        /**
094         * ResultSetMetaData で求めた、カラム数を返します。
095         *
096         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
097         *
098         * @return  カラム数(データの列数)
099         */
100        public int getColumnCount() { return clmSize ; }
101
102        /**
103         * カラム名配列を返します。
104         *
105         * 配列は、0から始まり、カラム数-1 までの文字型配列に設定されます。
106         * カラム名は、ResultSetMetaData#getColumnLabel(int) を toUpperCase した
107         * 大文字が返されます。
108         *
109         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
110         *
111         * @return      カラム名配列
112         * @og.rtnNotNull
113         */
114        public String[] getNames() {
115                return names.clone();
116        }
117
118        /**
119         * 指定のカラム番号のカラム名を返します。
120         *
121         * カラム名を取得する、カラム番号は、0から始まり、カラム数-1 までの数字で指定します。
122         * データベース上の、1から始まる番号とは、異なります。
123         * カラム名は、ResultSetMetaData#getColumnLabel(int) を toUpperCase した
124         * 大文字が返されます。
125         *
126         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
127         *
128         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
129         * @return  指定のカラム番号のカラム名
130         */
131        public String getColumnName( final int clmNo ) {
132                return names[clmNo];
133        }
134
135//      /**
136//       * java.sql.Types の定数定義配列を返します。
137//       *
138//       * 配列は、0から始まり、カラム数-1 までのint型配列に設定されます。
139//       * Types の定数定義は、ResultSetMetaData#getColumnType(int) の値です。
140//       *
141//       * @og.rev 6.0.4.0 (2014/11/28) 新規作成
142//       *
143//       * @return      カラム(0〜カラム数-1)に対応した、Types定数定義配列
144//       */
145//      public int[] getSqlTypes() {
146//              return type.clone();
147//      }
148
149        /**
150         * 指定のカラム番号のjava.sql.Types の定数定義を返します。
151         *
152         * 配列は、0から始まり、カラム数-1 までのint型配列に設定されます。
153         * Types の定数定義は、ResultSetMetaData#getColumnType(int) の値です。
154         *
155         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
156         *
157         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
158         * @return      指定のカラム番号のTypes定数
159         */
160        public int getSqlType( final int clmNo ) {
161                return type[clmNo];
162        }
163
164//      /**
165//       * オブジェクト型の Type が存在するかどうか[true:存在する/false:存在しない]を返します。
166//       *
167//       * 基本的には、値を求める処理の内部ロジックでのみ使用します。
168//       * Types.CLOB 、Types.ROWID、Types.TIMESTAMP が、Types定義に含まれている場合、
169//       * true を返します。
170//       * 含まれない場合は、false です。
171//       * オブジェクト型 が存在する場合、値を求める処理に、専用のメソッドが必要になります。
172//       * 含まれない場合は、String.valueOf( Object ) で求めることが可能です。
173//       * この値を参考に、呼び出すメソッドを変えることで、処理の高速化を図ろうと
174//       * 考えています。(効果があるかどうかは、判りません。)
175//       *
176//       * @og.rev 6.0.4.0 (2014/11/28) 新規作成
177//       *
178//       * @return  オブジェクト型の Type が存在するかどうか[true:存在する/false:存在しない]
179//       */
180//      public boolean useObjectType() {
181//              return useObj;
182//      }
183
184        /**
185         * カラムのサイズのint配列を返します。
186         *
187         * 配列は、0から始まり、カラム数-1 までのint型配列に設定されます。
188         * カラムのサイズは、ResultSetMetaData#getColumnDisplaySize(int) の値です。
189         *
190         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
191         *
192         * @return      カラムのサイズのint配列
193         * @og.rtnNotNull
194         */
195        public int[] getColumnDisplaySizes() {
196                return size.clone();
197        }
198
199        /**
200         * 指定のカラム番号のサイズを返します。
201         *
202         * カラムのサイズは、ResultSetMetaData#getColumnDisplaySize(int) の値です。
203         *
204         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
205         *
206         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
207         * @return      指定のカラム番号のサイズ
208         */
209        public int getColumnDisplaySize( final int clmNo ) {
210                return size[clmNo];
211        }
212
213        /**
214         * カラムの書き込み可能かどうかのboolean配列を返します。
215         *
216         * 配列は、0から始まり、カラム数-1 までのint型配列に設定されます。
217         * カラムの書き込み可能かどうかは、ResultSetMetaData#isWritable(int) の値です。
218         *
219         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
220         *
221         * @return      書き込み可能かどうかのboolean配列
222         * @og.rtnNotNull
223         */
224        public boolean[] isWritable() {
225                return isWrit.clone();
226        }
227
228        /**
229         * 指定の書き込み可能かどうかを返します。
230         *
231         * カラムの書き込み可能かどうかは、ResultSetMetaData#isWritable(int) の値です。
232         *
233         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
234         *
235         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
236         * @return      書き込み可能かどうか
237         */
238        public boolean isWritable( final int clmNo ) {
239                return isWrit[clmNo];
240        }
241
242        /**
243         * カーソルを現在の位置から順方向に1行移動します。
244         *
245         * ResultSet#next() を呼び出しています。
246         * 結果は,すべて文字列に変換されて格納されます。
247         *
248         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
249         *
250         * @return  新しい現在の行が有効である場合はtrue、行がそれ以上存在しない場合はfalse
251         * @see         java.sql.ResultSet#next()
252         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合、またはこのメソッドがクローズされた結果セットで呼び出された場合
253         */
254        public boolean next() throws SQLException {
255                return resultSet.next() ;
256        }
257
258        /**
259         * 現在のカーソル位置にあるレコードのカラム番号のデータを取得します。
260         *
261         * ResultSet#getObject( clmNo+1 ) を呼び出しています。
262         * 引数のカラム番号は、0から始まりますが、ResultSet のカラム順は、1から始まります。
263         * 指定は、0から始まるカラム番号です。
264         * 結果は,すべて文字列に変換されて返されます。
265         * また、null オブジェクトの場合も、ゼロ文字列に変換されて返されます。
266         *
267         * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getValue( ResultSet , int , int ) から移動
268         *
269         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
270         * @return  現在行のカラム番号のデータ(文字列)
271         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
272         */
273        public String getValue( final int clmNo ) throws SQLException {
274                final String val ;
275                final Object obj = resultSet.getObject( clmNo+1 );
276                if( obj == null ) {
277                        val = "";
278                }
279                else if( useObj ) {
280                        switch( type[clmNo] ) {
281                                case Types.CLOB :               val = getClobData( (Clob)obj ) ;
282                                                                                break;
283                                case Types.ROWID:               val = resultSet.getString(clmNo+1);
284                                                                                break;
285                                case Types.TIMESTAMP :  val = HybsDateUtil.getDate( ((Timestamp)obj).getTime() , "yyyyMMddHHmmss" );
286                                                                                break;
287                                default :                               val = String.valueOf( obj );
288                                                                                break;
289                        }
290                }
291                else {
292                        val = String.valueOf( obj );
293                }
294
295                return val ;
296        }
297
298        /**
299         * 現在のカーソル位置にあるレコードの全カラムデータを取得します。
300         *
301         * #getValue( clmNo ) を、0から、カラム数-1 まで呼び出して求めた文字列配列を返します。
302         *
303         * @og.rev 6.0.4.0 (2014/11/28) 新規作成
304         *
305         * @return  現在行の全カラムデータの文字列配列
306         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
307         */
308        public String[] getValues() throws SQLException {
309                final String[] vals = new String[clmSize];
310
311                for( int i=0; i<clmSize; i++ ) {
312                        vals[i] = getValue( i );
313                }
314
315                return vals ;
316        }
317
318        /**
319         * タイプに応じて変換された、Numberオブジェクトを返します。
320         *
321         * 条件に当てはまらない場合は、null を返します。
322         * org.opengion.hayabusa.io.HybsJDBCCategoryDataset2 から移動してきました。
323         * これは、検索結果をグラフ化する為の 値を取得する為のメソッドですので、
324         * 数値に変換できない場合は、エラーになります。
325         *
326         * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getNumber( int , Object ) から移動
327         *
328         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
329         * @return      Numberオブジェクト(条件に当てはまらない場合は、null)
330         * @see         java.sql.Types
331         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
332         * @throws      RuntimeException 数字変換できなかった場合。
333         */
334        public Number getNumber( final int clmNo ) throws SQLException {
335                final Object obj = resultSet.getObject( clmNo+1 );
336                Number value = null;
337
338                switch( type[clmNo] ) {
339                        case Types.TINYINT:
340                        case Types.SMALLINT:
341                        case Types.INTEGER:
342                        case Types.BIGINT:
343                        case Types.FLOAT:
344                        case Types.DOUBLE:
345                        case Types.DECIMAL:
346                        case Types.NUMERIC:
347                        case Types.REAL: {
348                                value = (Number)obj;
349                                break;
350                        }
351                        case Types.DATE:
352                        case Types.TIME:  {
353                                value = Long.valueOf( ((Date)obj).getTime() );
354                                break;
355                        }
356                        // 5.6.2.1 (2013/03/08) Types.DATE と Types.TIMESTAMP で処理を分けます。
357                        case Types.TIMESTAMP: {
358                                value = Long.valueOf( ((Timestamp)obj).getTime() );
359                                break;
360                        }
361                        case Types.CHAR:
362                        case Types.VARCHAR:
363                        case Types.LONGVARCHAR: {
364                                final String str = (String)obj;
365                                try {
366                                        value = Double.valueOf(str);
367                                }
368                                catch (NumberFormatException ex) {
369                                        final String errMsg = "数字変換できませんでした。in=" + str
370                                                                        + CR + ex.getMessage() ;
371                                        throw new RuntimeException( errMsg,ex );
372                                        // suppress (value defaults to null)
373                                }
374                                break;
375                        }
376                        default:
377                                // not a value, can't use it (defaults to null)
378                                break;
379                }
380
381                return value;
382        }
383
384        /**
385         * カラムのタイプを表現する文字列値を返します。
386         *
387         * この文字列を用いて、CCSファイルでタイプごとの表示方法を
388         * 指定することができます。
389         * 現時点では、VARCHAR2,LONG,NUMBER,DATE,CLOB,NONE のどれかにあてはめます。
390         *
391         * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#type2ClassName( int ) から移動
392         *
393         * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
394         * @return      カラムのタイプを表現する文字列値
395         * @see         java.sql.Types
396         */
397        public String getClassName( final int clmNo ) {
398                final String rtn ;
399
400                switch( type[clmNo] ) {
401                        case Types.CHAR:
402                        case Types.VARCHAR:
403                        case Types.BIT:
404                                rtn = "VARCHAR2"; break;
405                        case Types.LONGVARCHAR:
406                                rtn = "LONG"; break;
407                        case Types.TINYINT:
408                        case Types.SMALLINT:
409                        case Types.INTEGER:
410                        case Types.NUMERIC:
411                        case Types.BIGINT:
412                        case Types.FLOAT:
413                        case Types.DOUBLE:
414                        case Types.REAL:
415                        case Types.DECIMAL:
416                                rtn = "NUMBER"; break;
417                        case Types.DATE:
418                        case Types.TIME:
419                        case Types.TIMESTAMP:
420                                rtn = "DATE"; break;
421                        case Types.CLOB:
422                                rtn = "CLOB"; break;
423                        default:
424                                rtn = "NONE"; break;
425                }
426
427                return rtn;
428        }
429
430        /**
431         * Clob オブジェクトから文字列を取り出します。
432         *
433         * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getClobData( Clob ) から移動
434         *
435         * @param       clobData Clobオブジェクト
436         * @return      Clobオブジェクトから取り出した文字列
437         * @throws      SQLException データベースアクセスエラー
438         * @throws      RuntimeException 入出力エラーが発生した場合
439         * @og.rtnNotNull
440         */
441        private String getClobData( final Clob clobData ) throws SQLException {
442                if( clobData == null ) { return ""; }
443
444                final StringBuilder buf = new StringBuilder( 10000 );
445
446                Reader reader = null;
447                try {
448                        reader = clobData.getCharacterStream();
449                        final char[] ch = new char[10000];
450                        int  len ;
451                        while( (len = reader.read( ch )) >= 0 ) {
452                                buf.append( ch,0,len );
453                        }
454                }
455                catch( IOException ex ) {
456                        final String errMsg = "CLOBデータの読み込みに失敗しました。"
457                                                                + ex.getMessage() ;
458                        throw new RuntimeException( errMsg,ex );
459                }
460                finally {
461                        Closer.ioClose( reader );
462                }
463                return buf.toString();
464        }
465}