    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 ＤＢ制御
    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}