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) データ0件で、処理を抜けるので、初期値を与えておきます。 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) データ0件のときは、処理を中断します。 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( Statement statement = conn.createStatement(); 154 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の引数は、配列+1から始まるので、metaDataはi+2から取得。series と、seriesLabels は0から始まる。 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 分と、アドレスが1から始まる為。) 210 // ResultSetValueのカラム番号は、+1 する。(category 分があるため) 211 value = rsv.getNumber( i+1 ); 212 } 213 // 7.2.9.5 (2020/11/28) PMD:'catch' branch identical to 'SQLException' branch 214 catch( final SQLException | RuntimeException ex ) { // 6.0.4.0 (2014/11/28) ResultSetValue を使用するので。 215 LogWriter.log( ex ); 216 } 217// catch( final SQLException ex ) { // 6.0.4.0 (2014/11/28) ResultSetValue を使用するので。 218// LogWriter.log( ex ); 219// } 220// catch( final RuntimeException ex ) { 221// LogWriter.log( ex ); 222// } 223 224 clmList[i] = value; 225 addValue(value, series[i], category); // 6.0.2.0 (2014/09/19) columnKeyは、series , rowKey は、category に変更する。 226 // Range 求め 227 if( value != null ) { 228 final double dbl = value.doubleValue(); 229 if( isParetoData ) { // 6.0.2.3 (2014/10/19) パレート図用合計 230 sum += dbl ; 231 } else { 232 if( dbl < minimum ) { minimum = dbl; } 233 if( maximum < dbl ) { maximum = dbl; } 234 } 235 } 236 } 237 rowList.add( clmList ); 238 // 6.0.2.2 (2014/10/03) ColorCategory は、最後のカラム 239 if( isColorCategory ) { 240 // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。 241 final String colStr = rsv.getValue(dataSize+1); // 最後のカラム 242 final Color color = ColorMap.getColorInstance( colStr ); // 6.0.2.1 (2014/09/26) StringUtil → ColorMap 243 colorList.add( color ); 244 } 245 } 246 // 6.9.7.0 (2018/05/14) データ0件のときは、処理を中断します。 247 if( rowList.isEmpty() ) { return; } 248 249 numdata = rowList.toArray( new Number[dataSize][rowList.size()] ); 250 } 251 252 // colorList が null でないかどうかで判定。 253 if( isColorCategory && colorList != null ) { 254 categoryColor = colorList.toArray( new Color[colorList.size()] ); 255 } 256 257 // 6.0.2.3 (2014/10/19) パレート図は、100分率にする。 258 if( isParetoData ) { 259 changeParetoData( sum ); 260 minimum = 0.0; 261 maximum = 100.0; 262 } 263 264 range = new Range( minimum, maximum ); 265 } 266 267 /** 268 * DBTableModelオブジェクトから、CategoryDataset のデータを作成します。 269 * openGionの独自処理メソッドです。 270 * 271 * このメソッドでは、先に #initParam(String[],boolean,isPareto) のパラメータを使用して 272 * 検索した結果のデータを加工、処理します。 273 * また、内部的に、データをキャッシュする事と、データ範囲を示す レンジオブジェクト を作成します。 274 * 275 * @og.rev 6.0.2.2 (2014/10/03) 新規追加 276 * @og.rev 6.0.2.3 (2014/10/19) パレート図は、100分率にする。 277 * @og.rev 6.9.7.0 (2018/05/14) データ0件のときは、処理を中断します。 278 * 279 * @param table DBTableModelオブジェクト 280 * @see #execute( Connection,String ) 281 */ 282 public void execute( final DBTableModel table ) { 283 284 // 6.9.7.0 (2018/05/14) データ0件のときは、処理を中断します。 285 if( table == null || table.getRowCount() == 0 ) { return; } 286 287 final int clmNo = table.getColumnCount(); 288 final int rowNo = table.getRowCount(); 289 290 // Range を予め求めておきます。 291 double minimum = Double.POSITIVE_INFINITY; 292 double maximum = Double.NEGATIVE_INFINITY; 293 double sum = 0.0d; // 6.0.2.3 (2014/10/19) パレート図用合計 294 295 int dataSize = clmNo -1; // series の個数は、category 分を引いた数。 296 List<Color> colorList = null; // 6.0.2.2 (2014/10/03) カテゴリカラー 297 if( isColorCategory ) { // ColorCategory使用時 298 colorList = new ArrayList<>(); // カテゴリカラー 299 dataSize--; // 最終カラムが Colorコードなので、マイナスする。 300 } 301 302 numdata = new Number[rowNo][clmNo]; 303 304 // ※ DBTableModel の row,col と、Dataset の row,col は、逆になっています。 305 for( int row=0; row<rowNo; row++ ) { 306 final String category = uniqCategory( table.getValue( row,0 ) ); // 6.0.2.3 (2014/10/10) categoryの重複回避 307 final String[] vals = table.getValues( row ); 308 for( int clm=0; clm<dataSize; clm++ ) { 309 final String sval = vals[clm+1]; // 2番目(アドレス=1)からカラムデータを取得 310 final double val = sval == null || sval.isEmpty() ? 0.0d : Double.parseDouble( sval ) ; // 6.4.2.1 (2016/02/05) PMD refactoring. Useless parentheses. 311 312 addValue( val , seriesLabels[clm] , category ); // val,row,clm 313 numdata[row][clm] = Double.valueOf( val ); // 6.0.2.4 (2014/10/17) 効率の悪いメソッド 314 // Range 求め 315 if( isParetoData ) { // 6.0.2.3 (2014/10/19) パレート図用合計 316 sum += val ; 317 } else { 318 if( val < minimum ) { minimum = val; } 319 if( maximum < val ) { maximum = val; } 320 } 321 } 322 323 // 6.0.2.2 (2014/10/03) ColorCategory は、最後のカラム 324 if( isColorCategory ) { 325 final String colStr = vals[dataSize+1]; // 最後のカラム 326 final Color color = ColorMap.getColorInstance( colStr ); // 6.0.2.1 (2014/09/26) StringUtil → ColorMap 327 colorList.add( color ); 328 } 329 } 330 331 // colorList が null でないかどうかで判定。 332 if( isColorCategory && colorList != null ) { 333 categoryColor = colorList.toArray( new Color[colorList.size()] ); 334 } 335 336 // 6.0.2.3 (2014/10/19) パレート図は、100分率にする。 337 if( isParetoData ) { 338 changeParetoData( sum ); 339 minimum = 0.0; 340 maximum = 100.0; 341 } 342 343 range = new Range( minimum, maximum ); 344 } 345 346 /** 347 * 指定された行列から、数字オブジェクトを取得します。 348 * 349 * @param row 行番号(シリーズ:横持=clm相当) 350 * @param column カラム番号(カテゴリ:縦持ち=row相当) 351 * 352 * @return 指定の行列の値 353 */ 354 @Override 355 public Number getValue( final int row, final int column ) { 356 // 注意:行列の順序が逆です。 357 return numdata[column][row]; 358 } 359 360 /** 361 * レンジオブジェクトを取得します。(独自メソッド) 362 * 363 * @return レンジオブジェクト 364 */ 365 public Range getRange() { 366 return range; 367 } 368 369 /** 370 * パレート図用のDatasetに値を書き換えます。(独自メソッド) 371 * 372 * 色々と方法はあると思いますが、簡易的に、内部の Number配列を 373 * 積上げ計算して、パレート図用のデータを作成します。 374 * レンジオブジェクト も変更します。 375 * 376 * ※ 注意:親クラスの内部に持っている実データは変更されていないので、 377 * 場合によっては、おかしな動きをするかもしれません。 378 * その場合は、上位にもデータをセットするように変更する必要があります。 379 * 380 * なお、行列の順序が、イメージと異なりますので、注意願います。 381 * (columnは、series , row は、category で、シリーズを積み上げます) 382 * 383 * @og.rev 6.0.2.1 (2014/09/26) 新規追加 384 * @og.rev 6.0.2.2 (2014/10/03) HybsDataset i/f 385 * @og.rev 6.0.2.3 (2014/10/19) パレート図は、100分率にする。 386 * 387 * @param sum データの合計 388 */ 389 private void changeParetoData( final double sum ) { 390 if( numdata == null || numdata.length == 0 || numdata[0].length == 0 || sum == 0.0 ) { return ; } 391 392 final int rowCnt = numdata[0].length ; 393 final int clmCnt = numdata.length ; 394 395 for( int rowNo=0; rowNo<rowCnt; rowNo++ ) { // 行列が逆。 396 double val = 0.0; // 初期値 397 for( int clmNo=0; clmNo<clmCnt; clmNo++ ) { // 積上げ計算するカラムでループを回す。 398 final Number v1Num = numdata[clmNo][rowNo]; 399 if( v1Num != null ) { 400 val += v1Num.doubleValue(); // 積上げ計算は、元の値のままにしておきます。 401 } 402 // データをセットするときに、100分率にします。 403 numdata[clmNo][rowNo] = Double.valueOf( Math.round( val * 1000.0 / sum ) / 10.0 ); 404 // きちんと計算するなら、BigDecimal で、スケールを指定して四捨五入すべき・・・かも 405 // java.math.BigDecimal bd = new BigDecimal( val * 100.0 / sum ); 406 // numdata[clmNo][rowNo] = bd.setScale( 1, java.math.RoundingMode.HALF_UP ); 407 } 408 } 409 } 410 411 /** 412 * categoryカラー配列を取得します。(独自メソッド) 413 * 414 * このクラスは、一番最後のカラムを、色文字列として処理し、categoryにColorを指定できます。 415 * select文で指定されていなかった場合は、null を返します。 416 * 417 * select category,series1,series2,series3,・・・,color from ・・・ 418 * 419 * @og.rev 6.0.2.2 (2014/10/03) 新規追加 420 * 421 * なお、Colorコードは、このクラスで作成しますが、Renderer に与える必要があります。 422 * 通常のRenderer には、categoryにカラーを指定する機能がありませんので、HybsBarRenderer に 423 * setCategoryColor( Color[] ) メソッドを用意します。(正確には、HybsDrawItem インターフェース) 424 * このRenderer で、getItemPaint( int , int )メソッドをオーバーライドすることで、カテゴリごとの 425 * 色を返します。 426 * この設定を行うと、シリーズは、カテゴリと同一色になります。 427 * 428 * @return categoryカラー配列(なければ null) 429 */ 430 public Color[] getCategoryColor() { 431 // 6.0.2.5 (2014/10/31) refactoring 432 return ( categoryColor == null ) ? null : categoryColor.clone(); 433 } 434 435 /** 436 * category の重複をさけて、必要であれば、新しいカテゴリ名を作成します。 437 * 438 * カテゴリが同じ場合、JFreeChartでは、表示されません。これは、同じカテゴリと認識され 439 * 値が上書きされるためです。 440 * この問題は、なかなか気づきにくく、デバッグ等に時間がかかってしまいます。 441 * 重複チェックを行い、警告してもよいのですが、ここでは、新しいカテゴリ名を作成することで 442 * エラーを回避しつつ、とりあえずグラフ表示をするようにします。 443 * 444 * @og.rev 6.0.2.3 (2014/10/10) 新規追加 445 * 446 * @param category 元のカテゴリ名 447 * @return 新しい元のカテゴリ名 448 */ 449 private String uniqCategory( final String category ) { 450 String newCate = category ; 451 int i = 0; 452 while( !cateCheck.add( newCate ) ) { // すでに存在している場合。 453 newCate = category + "(" + (i++ ) + ")" ; 454 } 455 456 return newCate ; 457 } 458 459 /** 460 * この文字列と指定されたオブジェクトを比較します。 461 * 462 * 親クラスで、equals メソッドが実装されているため、警告がでます。 463 * 464 * @og.rev 5.1.8.0 (2010/07/01) findbug対応 465 * @og.rev 5.1.9.0 (2010/08/01) findbug対応 466 * 467 * @param object 比較するオブジェクト 468 * 469 * @return Objectが等しい場合は true、そうでない場合は false 470 */ 471 @Override 472 public boolean equals( final Object object ) { 473 // 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 474 return super.equals( object ) && hsCode == ((HybsCategoryDataset)object).hsCode; 475 } 476 477 /** 478 * このオブジェクトのハッシュコードを取得します。 479 * 480 * @og.rev 5.1.8.0 (2010/07/01) findbug対応 481 * @og.rev 5.1.9.0 (2010/08/01) findbug対応 482 * 483 * @return ハッシュコード 484 */ 485 @Override 486 public int hashCode() { return hsCode ; } 487}