    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.hayabusa.io;
    017
    018import static org.opengion.fukurou.system.HybsConst.CR ;                        // 6.1.0.0 (2014/12/26)
    019import static org.opengion.fukurou.system.HybsConst.DB_FETCH_SIZE;      // 6.9.4.1 (2018/04/09)
    020import org.opengion.fukurou.system.LogWriter;
    021import org.opengion.fukurou.util.ColorMap;                      // 6.0.2.2 (2014/10/03)
    022import org.opengion.fukurou.db.ResultSetValue;          // 6.0.4.0 (2014/11/28)
    023import org.opengion.hayabusa.db.DBTableModel;
    024// import org.opengion.hayabusa.common.HybsSystem;              // 6.9.3.0 (2018/03/26)6.9.3.0 (2018/03/26)
    025
    026import java.sql.Connection;
    027import java.sql.ResultSet;
    028import java.sql.SQLException;
    029import java.sql.Statement;
    030
    031import java.util.List;
    032import java.util.ArrayList;
    033import java.util.Arrays;
    034import java.util.Set;
    035import java.util.HashSet;
    036
    037import java.awt.Color;                                                          // 6.0.2.2 (2014/10/03)
    038
    039import org.jfree.data.Range;
    040import org.jfree.data.category.DefaultCategoryDataset;
    041
    042/**
    043 * HybsCategoryDataset は、org.jfree.data.category.DefaultCategoryDataset を継承したサブクラスで、
    044 * HybsDataset インターフェースの実装クラスになっています。
    045 * これは、JDBCCategoryDatasetの データベース機能と、DBTableModel から Dataset を作成する機能を
    046 * 兼ね備えています。
    047 * HybsDataset インターフェースは、シリーズのラベル指定、カテゴリカラーバー、パレート図用積上げ
    048 * 計算などの処理を行うための、インターフェースで、それらの処理も、HybsCategoryDataset に実装します。
    049 *
    050 * このクラスでは、検索結果を内部で持っておき、getValue(int row, int column)
    051 * メソッドで直接値を返します。
    052 *
    053 * select category,series1,series2,series3,･･･ from ･･･
    054 * series の横持ち(標準と同じ) 対応です。
    055 * category カラムの値は、カテゴリのラベルになり、series1,2,3 のラベルがシリーズラベル、値が
    056 * seriesの値になります。
    057 *
    058 * カテゴリのカラー名の指定を行う場合、最後のカラムが、カラー名の文字列になります。
    059 * select category,series1,series2,series3,･･･,color from ･･･
    060 * color文字列の検索結果は、Dataset には含まれません。
    061 *
    062 * その場合、color カラムがシリーズとして認識されない様に、ChartDatasetTag で、useCategoryColor="true"
    063 * を指定しておく必要があります。このフラグは、HybsCategoryDataset を使う処理以外では効果が
    064 * ありません（シリーズとして使用されてしまう）のでご注意ください。
    065 * このフラグは、カテゴリカラーバーを使う場合には必要ですが、カテゴリカラーバーと（例えばパレート図）
    066 * を合成する場合に、パレート図側にも useCategoryColor="true" を設定しておけば、同じSQL または、
    067 * DBTableModel を使う事ができるというためのフラグです。
    068 *
    069 * なお、Colorコードは、このクラスで作成しますが、Renderer に与える必要があります。
    070 * 通常のRenderer には、categoryにカラーを指定する機能がありませんので、HybsBarRenderer に
    071 * setCategoryColor( Color[] ) メソッドを用意します。(正確には、HybsDrawItem インターフェース)
    072 * このRenderer で、getItemPaint( int  , int )メソッドをオーバーライドすることで、カテゴリごとの
    073 * 色を返します。
    074 *
    075 * @og.rev 6.0.2.2 (2014/10/03) 新規追加
    076 *
    077 * @version  6.0.2.2 (2014/10/03)
    078 * @author   Kazuhiko Hasegawa
    079 * @since    JDK1.6,
    080 */
    081public class HybsCategoryDataset extends DefaultCategoryDataset implements HybsDataset {
    082        private static final long serialVersionUID = 602220141003L ;
    083
    084//      /** 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズ  {@value} */
    085//      private static final int DB_FETCH_SIZE = HybsSystem.sysInt( "DB_FETCH_SIZE" ) ;
    086
    087        private final Set<String> cateCheck     = new HashSet<>();                      // category の重複チェック
    088        private final int       hsCode  = Long.valueOf( System.nanoTime() ).hashCode() ;        // 5.1.9.0 (2010/08/01) equals,hashCode
    089
    090        private String[]        seriesLabels            ;
    091        private boolean         isColorCategory         ;                                               // 6.0.2.2 (2014/10/03)
    092        private boolean         isParetoData            ;                                               // 6.0.2.2 (2014/10/03)
    093
    094        private Number[][]      numdata                         ;
    095        private Color[]         categoryColor           ;
    096        private Range           range                           = new Range( 0, 0 );    // 6.9.7.0 (2018/05/14) データ０件で、処理を抜けるので、初期値を与えておきます。
    097
    098        /**
    099         * デフォルトコンストラクター
    100         *
    101         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
    102         */
    103        public HybsCategoryDataset() { super(); }               // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    104
    105        /**
    106         * CategoryDataset を構築するに当たり、初期パラメータを設定します。
    107         *
    108         * @og.rev 6.0.2.2 (2014/10/03) 新規追加
    109         *
    110         * @param lbls  シリーズのラベル名配列
    111         * @param isColCate  カテゴリのカラー名の指定有無(true:使用する)
    112         * @param isPareto   パレート図用のDatasetとして処理するかどうか(true:処理する)
    113         */
    114        public void initParam( final String[] lbls , final boolean isColCate , final boolean isPareto ) {
    115                // 6.0.2.5 (2014/10/31) refactoring
    116                if( lbls != null ) { seriesLabels = lbls.clone(); }
    117                isColorCategory = isColCate;
    118                isParetoData    = isPareto;
    119        }
    120
    121        /**
    122         * コネクションと、SQL文字列から、CategoryDataset のデータを作成します。
    123         * 元となる処理は、org.jfree.data.jdbc.JDBCCategoryDataset#executeQuery( Connection,String ) です。
    124         *
    125         * このメソッドでは、先に #initParam(String[],boolean,isPareto) のパラメータを使用して
    126         * 検索した結果のデータを加工、処理します。
    127         * また、内部的に、データをキャッシュする事と、データ範囲を示す レンジオブジェクト を作成します。
    128         *
    129         * @og.rev 6.0.2.2 (2014/10/03) 新規追加
    130         * @og.rev 6.0.2.3 (2014/10/19) パレート図は、100分率にする。
    131         * @og.rev 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
    132         * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。
    133         * @og.rev 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズを設定。
    134         * @og.rev 6.9.7.0 (2018/05/14) データ０件のときは、処理を中断します。
    135         *
    136         * @param conn  コネクション
    137         * @param query  SQL文字列
    138         *
    139         * @throws SQLException データベースアクセス時のエラー
    140         * @see         org.jfree.data.jdbc.JDBCCategoryDataset#executeQuery( Connection,String )
    141         * @see         org.opengion.fukurou.db.ResultSetValue
    142         */
    143        public void execute( final Connection conn, final String query ) throws SQLException {
    144
    145                // Range を予め求めておきます。
    146                double minimum = Double.POSITIVE_INFINITY;
    147                double maximum = Double.NEGATIVE_INFINITY;
    148                double sum     = 0.0d;                                  // 6.0.2.3 (2014/10/19) パレート図用合計
    149
    150                List<Color> colorList = null;                   // 6.0.2.2 (2014/10/03) カテゴリカラー
    151
    152                // 6.4.2.1 (2016/02/05) try-with-resources 文
    153                try( final Statement statement = conn.createStatement();
    154                        final ResultSet resultSet = statement.executeQuery(query) ) {
    155
    156                        statement.setFetchSize( DB_FETCH_SIZE );                                // 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズ
    157
    158                        // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
    159                        final ResultSetValue rsv = new ResultSetValue( resultSet );
    160
    161                        int dataSize = rsv.getColumnCount() -1;                 // series の個数は、category 分を引いた数。
    162                        if( isColorCategory ) {                                                 // ColorCategory使用時
    163                                colorList       = new ArrayList<>();                    // カテゴリカラー
    164                                dataSize--;                                                                     // 最終カラムが Colorコードなので、マイナスする。
    165                        }
    166
    167                        if( dataSize<1 ) {
    168                                final String errMsg = "JDBCCategoryDataset.executeQuery() : insufficient columns "
    169                                                        + "returned from the database. \n"
    170                                                        + " SQL=" + query ;
    171                                throw new SQLException( errMsg );
    172                        }
    173
    174                        // 6.0.2.0 (2014/09/19) シリーズのラベル名配列を使うときは、シリーズ数必要。
    175                        if( seriesLabels != null && seriesLabels.length < dataSize ) {
    176                                final String errMsg = "seriesLabels を使用する場合は、必ずシリーズ数以上指定してください。"
    177                                                                + CR
    178                                                                + " seriesLabels=" + Arrays.toString( seriesLabels )
    179                                                                + CR
    180                                                                + " seriesLabels.length=" + seriesLabels.length
    181                                                                + " dataSize=" + dataSize
    182                                                                + CR ;
    183                                throw new IllegalArgumentException( errMsg );
    184                        }
    185
    186                        String[] series  = new String[dataSize];
    187                        // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
    188                        final String[] names   = rsv.getNames();
    189                        // ORACLEの引数は、配列＋１から始まるので、metaDataはi＋2から取得。series と、seriesLabels は０から始まる。
    190                        for( int i=0; i<dataSize; i++ ) {
    191                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    192                                series[i] = seriesLabels == null || seriesLabels[i] == null
    193                                                                        ? names[i+1]
    194                                                                        : seriesLabels[i] ;
    195                        }
    196
    197                        final List<Number[]> rowList = new ArrayList<>();
    198                        // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
    199                        while( rsv.next() ) {
    200                                Number[] clmList = new Number[dataSize];
    201                                // first column contains the row key...
    202                                // 6.0.2.0 (2014/09/19) columnKeyは、series , rowKey は、category に変更する。
    203                                final String category = uniqCategory( resultSet.getString(1) ); // 6.0.2.3 (2014/10/10) categoryの重複回避
    204
    205                                for( int i=0; i<dataSize; i++ ) {                                       // 6.0.2.2 (2014/10/03) dataSize 分回す。
    206                                        Number value = null;
    207                                        // 6.0.2.1 (2014/09/26) org.opengion.fukurou.db.DBUtil に、移動
    208                                        try {
    209                                                // JDBCのアドレス指定は、+2 する。(category 分と、ｱﾄﾞﾚｽが１から始まる為。)
    210                                                // ResultSetValueのカラム番号は、+1 する。(category 分があるため)
    211                                                value = rsv.getNumber( i+1 );
    212                                        }
    213                                        catch( final SQLException ex ) {                // 6.0.4.0 (2014/11/28) ResultSetValue を使用するので。
    214                                                LogWriter.log( ex );
    215                                        }
    216                                        catch( final RuntimeException ex ) {
    217                                                LogWriter.log( ex );
    218                                        }
    219
    220                                        clmList[i] = value;
    221                                        addValue(value, series[i], category);           // 6.0.2.0 (2014/09/19) columnKeyは、series , rowKey は、category に変更する。
    222                                        // Range 求め
    223                                        if( value != null ) {
    224                                                final double dbl = value.doubleValue();
    225                                                if( isParetoData ) {                                    // 6.0.2.3 (2014/10/19) パレート図用合計
    226                                                        sum += dbl ;
    227                                                } else {
    228                                                        if( dbl     < minimum ) { minimum = dbl; }
    229                                                        if( maximum < dbl     ) { maximum = dbl; }
    230                                                }
    231                                        }
    232                                }
    233                                rowList.add( clmList );
    234                                // 6.0.2.2 (2014/10/03) ColorCategory は、最後のカラム
    235                                if( isColorCategory ) {
    236                                        // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
    237                                        final String colStr = rsv.getValue(dataSize+1);                         // 最後のカラム
    238                                        final Color color   = ColorMap.getColorInstance( colStr );      // 6.0.2.1 (2014/09/26) StringUtil → ColorMap
    239                                        colorList.add( color );
    240                                }
    241                        }
    242                        // 6.9.7.0 (2018/05/14) データ０件のときは、処理を中断します。
    243                        if( rowList.isEmpty() ) { return; }
    244
    245                        numdata = rowList.toArray( new Number[dataSize][rowList.size()] );
    246                }
    247
    248                // colorList が null でないかどうかで判定。
    249                if( isColorCategory && colorList != null ) {
    250                        categoryColor = colorList.toArray( new Color[colorList.size()] );
    251                }
    252
    253                // 6.0.2.3 (2014/10/19) パレート図は、100分率にする。
    254                if( isParetoData ) {
    255                        changeParetoData( sum );
    256                        minimum = 0.0;
    257                        maximum = 100.0;
    258                }
    259
    260                range = new Range( minimum, maximum );
    261        }
    262
    263        /**
    264         * DBTableModelオブジェクトから、CategoryDataset のデータを作成します。
    265         * openGionの独自処理メソッドです。
    266         *
    267         * このメソッドでは、先に #initParam(String[],boolean,isPareto) のパラメータを使用して
    268         * 検索した結果のデータを加工、処理します。
    269         * また、内部的に、データをキャッシュする事と、データ範囲を示す レンジオブジェクト を作成します。
    270         *
    271         * @og.rev 6.0.2.2 (2014/10/03) 新規追加
    272         * @og.rev 6.0.2.3 (2014/10/19) パレート図は、100分率にする。
    273         * @og.rev 6.9.7.0 (2018/05/14) データ０件のときは、処理を中断します。
    274         *
    275         * @param table DBTableModelオブジェクト
    276         * @see         #execute( Connection,String )
    277         */
    278        public void execute( final DBTableModel table ) {
    279
    280                // 6.9.7.0 (2018/05/14) データ０件のときは、処理を中断します。
    281                if( table == null || table.getRowCount() == 0 ) { return; }
    282
    283                final int clmNo = table.getColumnCount();
    284                final int rowNo = table.getRowCount();
    285
    286                // Range を予め求めておきます。
    287                double minimum = Double.POSITIVE_INFINITY;
    288                double maximum = Double.NEGATIVE_INFINITY;
    289                double sum     = 0.0d;                                  // 6.0.2.3 (2014/10/19) パレート図用合計
    290
    291                int dataSize = clmNo -1;                                                // series の個数は、category 分を引いた数。
    292                List<Color> colorList = null;                   // 6.0.2.2 (2014/10/03) カテゴリカラー
    293                if( isColorCategory ) {                                                 // ColorCategory使用時
    294                        colorList       = new ArrayList<>();            // カテゴリカラー
    295                        dataSize--;                                                                     // 最終カラムが Colorコードなので、マイナスする。
    296                }
    297
    298                numdata = new Number[rowNo][clmNo];
    299
    300                // ※ DBTableModel の row,col と、Dataset の row,col は、逆になっています。
    301                for( int row=0; row<rowNo; row++ ) {
    302                        final String   category = uniqCategory( table.getValue( row,0 ) );      // 6.0.2.3 (2014/10/10) categoryの重複回避
    303                        final String[] vals     = table.getValues( row );
    304                        for( int clm=0; clm<dataSize; clm++ ) {
    305                                final String sval = vals[clm+1];                                // ２番目(アドレス=1)からカラムデータを取得
    306                                final double val  = sval == null || sval.isEmpty() ? 0.0d : Double.parseDouble( sval ) ;                // 6.4.2.1 (2016/02/05) PMD refactoring. Useless parentheses.
    307
    308                                addValue( val , seriesLabels[clm] , category );         // val,row,clm
    309                                numdata[row][clm] = Double.valueOf( val );                      // 6.0.2.4 (2014/10/17) 効率の悪いメソッド
    310                                // Range 求め
    311                                if( isParetoData ) {                                    // 6.0.2.3 (2014/10/19) パレート図用合計
    312                                        sum += val ;
    313                                } else {
    314                                        if( val     < minimum ) { minimum = val; }
    315                                        if( maximum < val     ) { maximum = val; }
    316                                }
    317                        }
    318
    319                        // 6.0.2.2 (2014/10/03) ColorCategory は、最後のカラム
    320                        if( isColorCategory ) {
    321                                final String colStr = vals[dataSize+1];                 // 最後のカラム
    322                                final Color color   = ColorMap.getColorInstance( colStr );      // 6.0.2.1 (2014/09/26) StringUtil → ColorMap
    323                                colorList.add( color );
    324                        }
    325                }
    326
    327                // colorList が null でないかどうかで判定。
    328                if( isColorCategory && colorList != null ) {
    329                        categoryColor = colorList.toArray( new Color[colorList.size()] );
    330                }
    331
    332                // 6.0.2.3 (2014/10/19) パレート図は、100分率にする。
    333                if( isParetoData ) {
    334                        changeParetoData( sum );
    335                        minimum = 0.0;
    336                        maximum = 100.0;
    337                }
    338
    339                range = new Range( minimum, maximum );
    340        }
    341
    342        /**
    343         * 指定された行列から、数字オブジェクトを取得します。
    344         *
    345         * @param       row     行番号(シリーズ：横持=clm相当)
    346         * @param       column  カラム番号(カテゴリ：縦持ち=row相当)
    347         *
    348         * @return      指定の行列の値
    349         */
    350        @Override
    351        public Number getValue( final int row, final int column ) {
    352                // 注意：行列の順序が逆です。
    353                return numdata[column][row];
    354        }
    355
    356        /**
    357         * レンジオブジェクトを取得します。(独自メソッド)
    358         *
    359         * @return      レンジオブジェクト
    360         */
    361        public Range getRange() {
    362                return range;
    363        }
    364
    365        /**
    366         * パレート図用のDatasetに値を書き換えます。(独自メソッド)
    367         *
    368         * 色々と方法はあると思いますが、簡易的に、内部の Number配列を
    369         * 積上げ計算して、パレート図用のデータを作成します。
    370         * レンジオブジェクト も変更します。
    371         *
    372         * ※ 注意：親クラスの内部に持っている実データは変更されていないので、
    373         * 場合によっては、おかしな動きをするかもしれません。
    374         * その場合は、上位にもデータをセットするように変更する必要があります。
    375         *
    376         * なお、行列の順序が、イメージと異なりますので、注意願います。
    377         * (columnは、series , row は、category で、シリーズを積み上げます)
    378         *
    379         * @og.rev 6.0.2.1 (2014/09/26) 新規追加
    380         * @og.rev 6.0.2.2 (2014/10/03) HybsDataset i/f
    381         * @og.rev 6.0.2.3 (2014/10/19) パレート図は、100分率にする。
    382         *
    383         * @param  sum データの合計
    384         */
    385        private void changeParetoData( final double sum ) {
    386                if( numdata == null || numdata.length == 0 || numdata[0].length == 0 || sum == 0.0 ) { return ; }
    387
    388                final int rowCnt = numdata[0].length ;
    389                final int clmCnt = numdata.length ;
    390
    391                for( int rowNo=0; rowNo<rowCnt; rowNo++ ) {                     // 行列が逆。
    392                        double val = 0.0;               // 初期値
    393                        for( int clmNo=0; clmNo<clmCnt; clmNo++ ) {             // 積上げ計算するカラムでループを回す。
    394                                final Number v1Num = numdata[clmNo][rowNo];
    395                                if( v1Num != null ) {
    396                                        val += v1Num.doubleValue();                             // 積上げ計算は、元の値のままにしておきます。
    397                                }
    398                                // データをセットするときに、１００分率にします。
    399                                numdata[clmNo][rowNo] = Double.valueOf( Math.round( val * 1000.0 / sum ) / 10.0 );
    400        // きちんと計算するなら、BigDecimal で、スケールを指定して四捨五入すべき・・・かも
    401        //                      java.math.BigDecimal bd = new BigDecimal( val * 100.0 / sum );
    402        //                      numdata[clmNo][rowNo] = bd.setScale( 1, java.math.RoundingMode.HALF_UP );
    403                        }
    404                }
    405        }
    406
    407        /**
    408         * categoryカラー配列を取得します。(独自メソッド)
    409         *
    410         * このクラスは、一番最後のカラムを、色文字列として処理し、categoryにColorを指定できます。
    411         * select文で指定されていなかった場合は、null を返します。
    412         *
    413         * select category,series1,series2,series3,･･･,color from ･･･
    414         *
    415         * @og.rev 6.0.2.2 (2014/10/03) 新規追加
    416         *
    417         * なお、Colorコードは、このクラスで作成しますが、Renderer に与える必要があります。
    418         * 通常のRenderer には、categoryにカラーを指定する機能がありませんので、HybsBarRenderer に
    419         * setCategoryColor( Color[] ) メソッドを用意します。(正確には、HybsDrawItem インターフェース)
    420         * このRenderer で、getItemPaint( int  , int )メソッドをオーバーライドすることで、カテゴリごとの
    421         * 色を返します。
    422         * この設定を行うと、シリーズは、カテゴリと同一色になります。
    423         *
    424         * @return      categoryカラー配列(なければ null)
    425         */
    426        public Color[] getCategoryColor() {
    427                // 6.0.2.5 (2014/10/31) refactoring
    428                return ( categoryColor == null ) ? null : categoryColor.clone();
    429        }
    430
    431        /**
    432         * category の重複をさけて、必要であれば、新しいカテゴリ名を作成します。
    433         *
    434         * カテゴリが同じ場合、JFreeChartでは、表示されません。これは、同じカテゴリと認識され
    435         * 値が上書きされるためです。
    436         * この問題は、なかなか気づきにくく、デバッグ等に時間がかかってしまいます。
    437         * 重複チェックを行い、警告してもよいのですが、ここでは、新しいカテゴリ名を作成することで
    438         * エラーを回避しつつ、とりあえずグラフ表示をするようにします。
    439         *
    440         * @og.rev 6.0.2.3 (2014/10/10) 新規追加
    441         *
    442         * @param       category        元のカテゴリ名
    443         * @return      新しい元のカテゴリ名
    444         */
    445        private String uniqCategory( final String category ) {
    446                String newCate = category ;
    447                int i = 0;
    448                while( !cateCheck.add( newCate ) ) {    // すでに存在している場合。
    449                        newCate = category + "(" + (i++ ) + ")" ;
    450                }
    451
    452                return newCate ;
    453        }
    454
    455        /**
    456         * この文字列と指定されたオブジェクトを比較します。
    457         *
    458         * 親クラスで、equals メソッドが実装されているため、警告がでます。
    459         *
    460         * @og.rev 5.1.8.0 (2010/07/01) findbug対応
    461         * @og.rev 5.1.9.0 (2010/08/01) findbug対応
    462         *
    463         * @param       object  比較するオブジェクト
    464         *
    465         * @return      Objectが等しい場合は true、そうでない場合は false
    466         */
    467        @Override
    468        public boolean equals( final Object object ) {
    469                // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
    470                return super.equals( object ) && hsCode == ((HybsCategoryDataset)object).hsCode;
    471        }
    472
    473        /**
    474         * このオブジェクトのハッシュコードを取得します。
    475         *
    476         * @og.rev 5.1.8.0 (2010/07/01) findbug対応
    477         * @og.rev 5.1.9.0 (2010/08/01) findbug対応
    478         *
    479         * @return      ハッシュコード
    480         */
    481        @Override
    482        public int hashCode() { return hsCode ; }
    483}