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