KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jfree > data > jdbc > JDBCXYDataset


1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
6  *
7  * Project Info: http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22  * USA.
23  *
24  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
25  * in the United States and other countries.]
26  *
27  * ------------------
28  * JDBCXYDataset.java
29  * ------------------
30  * (C) Copyright 2002-2006, by Bryan Scott and Contributors.
31  *
32  * Original Author: Bryan Scott;
33  * Contributor(s): David Gilbert (for Object Refinery Limited);
34  * Eric Alexander;
35  *
36  *
37  * Changes
38  * -------
39  * 14-Mar-2002 : Version 1 contributed by Bryan Scott (DG);
40  * 19-Apr-2002 : Updated executeQuery, to close cursors and to improve support
41  * for types.
42  * 26-Apr-2002 : Renamed JdbcXYDataset to better fit in with the existing data
43  * source conventions.
44  * 26-Apr-2002 : Changed to extend AbstractDataset.
45  * 13-Aug-2002 : Updated Javadoc comments and imports (DG);
46  * 18-Sep-2002 : Updated to support BIGINT (BS);
47  * 21-Jan-2003 : Renamed JdbcXYDataset --> JDBCXYDataset (DG);
48  * 01-Jul-2003 : Added support to query whether a timeseries (BS);
49  * 30-Jul-2003 : Added empty contructor and executeQuery(connection,string)
50  * method (BS);
51  * 24-Sep-2003 : Added a check to ensure at least two valid columns are
52  * returned by the query in executeQuery as suggest in online
53  * forum by anonymous (BS);
54  * 02-Dec-2003 : Throwing exceptions allows to handle errors, removed default
55  * constructor, as without a connection, a query can never be
56  * executed.
57  * 16-Mar-2004 : Added check for null values (EA);
58  * 05-May-2004 : Now extends AbstractXYDataset (DG);
59  * 21-May-2004 : Implemented TableXYDataset, added support for SMALLINT and
60  * fixed bug in code that determines the min and max values (see
61  * bug id 938138) (DG);
62  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
63  * getYValue() (DG);
64  * 18-Nov-2004 : Updated for changes in RangeInfo interface (DG);
65  * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
66  * release (DG);
67  * ------------- JFREECHART 1.0.x ---------------------------------------------
68  * 17-Oct-2006 : Deprecated unused methods - see bug 1578293 (DG);
69  *
70  */

71
72 package org.jfree.data.jdbc;
73
74 import java.sql.Connection JavaDoc;
75 import java.sql.DriverManager JavaDoc;
76 import java.sql.ResultSet JavaDoc;
77 import java.sql.ResultSetMetaData JavaDoc;
78 import java.sql.SQLException JavaDoc;
79 import java.sql.Statement JavaDoc;
80 import java.sql.Types JavaDoc;
81 import java.util.ArrayList JavaDoc;
82 import java.util.Date JavaDoc;
83
84 import org.jfree.data.Range;
85 import org.jfree.data.RangeInfo;
86 import org.jfree.data.general.Dataset;
87 import org.jfree.data.xy.AbstractXYDataset;
88 import org.jfree.data.xy.TableXYDataset;
89 import org.jfree.data.xy.XYDataset;
90 import org.jfree.util.Log;
91
92 /**
93  * This class provides an {@link XYDataset} implementation over a database
94  * JDBC result set. The dataset is populated via a call to executeQuery with
95  * the string sql query. The sql query must return at least two columns.
96  * The first column will be the x-axis and remaining columns y-axis values.
97  * executeQuery can be called a number of times.
98  *
99  * The database connection is read-only and no write back facility exists.
100  */

101 public class JDBCXYDataset extends AbstractXYDataset
102                            implements XYDataset,
103                                       TableXYDataset,
104                                       RangeInfo {
105
106     /** The database connection. */
107     private transient Connection JavaDoc connection;
108
109     /** Column names. */
110     private String JavaDoc[] columnNames = {};
111
112     /** Rows. */
113     private ArrayList JavaDoc rows;
114
115     /** The maximum y value of the returned result set */
116     private double maxValue = 0.0;
117
118     /** The minimum y value of the returned result set */
119     private double minValue = 0.0;
120
121     /** Is this dataset a timeseries ? */
122     private boolean isTimeSeries = false;
123
124     /**
125      * Creates a new JDBCXYDataset (initially empty) with no database
126      * connection.
127      */

128     private JDBCXYDataset() {
129         this.rows = new ArrayList JavaDoc();
130     }
131
132     /**
133      * Creates a new dataset (initially empty) and establishes a new database
134      * connection.
135      *
136      * @param url URL of the database connection.
137      * @param driverName the database driver class name.
138      * @param user the database user.
139      * @param password the database user's password.
140      *
141      * @throws ClassNotFoundException if the driver cannot be found.
142      * @throws SQLException if there is a problem connecting to the database.
143      */

144     public JDBCXYDataset(String JavaDoc url,
145                          String JavaDoc driverName,
146                          String JavaDoc user,
147                          String JavaDoc password)
148         throws SQLException JavaDoc, ClassNotFoundException JavaDoc {
149         
150         this();
151         Class.forName(driverName);
152         this.connection = DriverManager.getConnection(url, user, password);
153     }
154
155     /**
156      * Creates a new dataset (initially empty) using the specified database
157      * connection.
158      *
159      * @param con the database connection.
160      *
161      * @throws SQLException if there is a problem connecting to the database.
162      */

163     public JDBCXYDataset(Connection JavaDoc con) throws SQLException JavaDoc {
164         this();
165         this.connection = con;
166     }
167
168     /**
169      * Creates a new dataset using the specified database connection, and
170      * populates it using data obtained with the supplied query.
171      *
172      * @param con the connection.
173      * @param query the SQL query.
174      *
175      * @throws SQLException if there is a problem executing the query.
176      */

177     public JDBCXYDataset(Connection JavaDoc con, String JavaDoc query) throws SQLException JavaDoc {
178         this(con);
179         executeQuery(query);
180     }
181
182     /**
183      * Returns <code>true</code> if the dataset represents time series data,
184      * and <code>false</code> otherwise.
185      *
186      * @return A boolean.
187      */

188     public boolean isTimeSeries() {
189         return this.isTimeSeries;
190     }
191
192     /**
193      * Sets a flag that indicates whether or not the data represents a time
194      * series.
195      *
196      * @param timeSeries the new value of the flag.
197      */

198     public void setTimeSeries(boolean timeSeries) {
199         this.isTimeSeries = timeSeries;
200     }
201
202     /**
203      * ExecuteQuery will attempt execute the query passed to it against the
204      * existing database connection. If no connection exists then no action
205      * is taken.
206      *
207      * The results from the query are extracted and cached locally, thus
208      * applying an upper limit on how many rows can be retrieved successfully.
209      *
210      * @param query the query to be executed.
211      *
212      * @throws SQLException if there is a problem executing the query.
213      */

214     public void executeQuery(String JavaDoc query) throws SQLException JavaDoc {
215         executeQuery(this.connection, query);
216     }
217
218     /**
219      * ExecuteQuery will attempt execute the query passed to it against the
220      * provided database connection. If connection is null then no action is
221      * taken.
222      *
223      * The results from the query are extracted and cached locally, thus
224      * applying an upper limit on how many rows can be retrieved successfully.
225      *
226      * @param query the query to be executed.
227      * @param con the connection the query is to be executed against.
228      *
229      * @throws SQLException if there is a problem executing the query.
230      */

231     public void executeQuery(Connection JavaDoc con, String JavaDoc query)
232         throws SQLException JavaDoc {
233
234         if (con == null) {
235             throw new SQLException JavaDoc(
236                 "There is no database to execute the query."
237             );
238         }
239
240         ResultSet JavaDoc resultSet = null;
241         Statement JavaDoc statement = null;
242         try {
243             statement = con.createStatement();
244             resultSet = statement.executeQuery(query);
245             ResultSetMetaData JavaDoc metaData = resultSet.getMetaData();
246
247             int numberOfColumns = metaData.getColumnCount();
248             int numberOfValidColumns = 0;
249             int [] columnTypes = new int[numberOfColumns];
250             for (int column = 0; column < numberOfColumns; column++) {
251                 try {
252                     int type = metaData.getColumnType(column + 1);
253                     switch (type) {
254
255                         case Types.NUMERIC:
256                         case Types.REAL:
257                         case Types.INTEGER:
258                         case Types.DOUBLE:
259                         case Types.FLOAT:
260                         case Types.DECIMAL:
261                         case Types.BIT:
262                         case Types.DATE:
263                         case Types.TIME:
264                         case Types.TIMESTAMP:
265                         case Types.BIGINT:
266                         case Types.SMALLINT:
267                             ++numberOfValidColumns;
268                             columnTypes[column] = type;
269                             break;
270                         default:
271                             Log.warn(
272                                 "Unable to load column "
273                                 + column + " (" + type + ","
274                                 + metaData.getColumnClassName(column + 1)
275                                 + ")"
276                             );
277                             columnTypes[column] = Types.NULL;
278                             break;
279                     }
280                 }
281                 catch (SQLException JavaDoc e) {
282                     columnTypes[column] = Types.NULL;
283                     throw e;
284                 }
285             }
286
287
288             if (numberOfValidColumns <= 1) {
289                 throw new SQLException JavaDoc(
290                     "Not enough valid columns where generated by query."
291                 );
292             }
293
294             /// First column is X data
295
this.columnNames = new String JavaDoc[numberOfValidColumns - 1];
296             /// Get the column names and cache them.
297
int currentColumn = 0;
298             for (int column = 1; column < numberOfColumns; column++) {
299                 if (columnTypes[column] != Types.NULL) {
300                     this.columnNames[currentColumn]
301                         = metaData.getColumnLabel(column + 1);
302                     ++currentColumn;
303                 }
304             }
305
306             // Might need to add, to free memory from any previous result sets
307
if (this.rows != null) {
308                 for (int column = 0; column < this.rows.size(); column++) {
309                     ArrayList JavaDoc row = (ArrayList JavaDoc) this.rows.get(column);
310                     row.clear();
311                 }
312                 this.rows.clear();
313             }
314
315             // Are we working with a time series.
316
switch (columnTypes[0]) {
317                 case Types.DATE:
318                 case Types.TIME:
319                 case Types.TIMESTAMP:
320                     this.isTimeSeries = true;
321                     break;
322                 default :
323                     this.isTimeSeries = false;
324                     break;
325             }
326
327             // Get all rows.
328
// rows = new ArrayList();
329
while (resultSet.next()) {
330                 ArrayList JavaDoc newRow = new ArrayList JavaDoc();
331                 for (int column = 0; column < numberOfColumns; column++) {
332                     Object JavaDoc xObject = resultSet.getObject(column + 1);
333                     switch (columnTypes[column]) {
334                         case Types.NUMERIC:
335                         case Types.REAL:
336                         case Types.INTEGER:
337                         case Types.DOUBLE:
338                         case Types.FLOAT:
339                         case Types.DECIMAL:
340                         case Types.BIGINT:
341                         case Types.SMALLINT:
342                             newRow.add(xObject);
343                             break;
344
345                         case Types.DATE:
346                         case Types.TIME:
347                         case Types.TIMESTAMP:
348                             newRow.add(new Long JavaDoc(((Date JavaDoc) xObject).getTime()));
349                             break;
350                         case Types.NULL:
351                             break;
352                         default:
353                             System.err.println("Unknown data");
354                             columnTypes[column] = Types.NULL;
355                             break;
356                     }
357                 }
358                 this.rows.add(newRow);
359             }
360
361             /// a kludge to make everything work when no rows returned
362
if (this.rows.size() == 0) {
363                 ArrayList JavaDoc newRow = new ArrayList JavaDoc();
364                 for (int column = 0; column < numberOfColumns; column++) {
365                     if (columnTypes[column] != Types.NULL) {
366                         newRow.add(new Integer JavaDoc(0));
367                     }
368                 }
369                 this.rows.add(newRow);
370             }
371
372             /// Determine max and min values.
373
if (this.rows.size() < 1) {
374                 this.maxValue = 0.0;
375                 this.minValue = 0.0;
376             }
377             else {
378                 ArrayList JavaDoc row = (ArrayList JavaDoc) this.rows.get(0);
379                 this.maxValue = Double.NEGATIVE_INFINITY;
380                 this.minValue = Double.POSITIVE_INFINITY;
381                 for (int rowNum = 0; rowNum < this.rows.size(); ++rowNum) {
382                     row = (ArrayList JavaDoc) this.rows.get(rowNum);
383                     for (int column = 1; column < numberOfColumns; column++) {
384                         Object JavaDoc testValue = row.get(column);
385                         if (testValue != null) {
386                             double test = ((Number JavaDoc) testValue).doubleValue();
387                         
388                             if (test < this.minValue) {
389                                 this.minValue = test;
390                             }
391                             if (test > this.maxValue) {
392                                 this.maxValue = test;
393                             }
394                         }
395                     }
396                 }
397             }
398
399             fireDatasetChanged(); // Tell the listeners a new table has arrived.
400
}
401         finally {
402             if (resultSet != null) {
403                 try {
404                     resultSet.close();
405                 }
406                 catch (Exception JavaDoc e) {
407                     // TODO: is this a good idea?
408
}
409             }
410             if (statement != null) {
411                 try {
412                     statement.close();
413                 }
414                 catch (Exception JavaDoc e) {
415                     // TODO: is this a good idea?
416
}
417             }
418         }
419
420     }
421
422     /**
423      * Returns the x-value for the specified series and item. The
424      * implementation is responsible for ensuring that the x-values are
425      * presented in ascending order.
426      *
427      * @param seriesIndex the series (zero-based index).
428      * @param itemIndex the item (zero-based index).
429      *
430      * @return The x-value
431      *
432      * @see XYDataset
433      */

434     public Number JavaDoc getX(int seriesIndex, int itemIndex) {
435         ArrayList JavaDoc row = (ArrayList JavaDoc) this.rows.get(itemIndex);
436         return (Number JavaDoc) row.get(0);
437     }
438
439     /**
440      * Returns the y-value for the specified series and item.
441      *
442      * @param seriesIndex the series (zero-based index).
443      * @param itemIndex the item (zero-based index).
444      *
445      * @return The yValue value
446      *
447      * @see XYDataset
448      */

449     public Number JavaDoc getY(int seriesIndex, int itemIndex) {
450         ArrayList JavaDoc row = (ArrayList JavaDoc) this.rows.get(itemIndex);
451         return (Number JavaDoc) row.get(seriesIndex + 1);
452     }
453
454     /**
455      * Returns the number of items in the specified series.
456      *
457      * @param seriesIndex the series (zero-based index).
458      *
459      * @return The itemCount value
460      *
461      * @see XYDataset
462      */

463     public int getItemCount(int seriesIndex) {
464         return this.rows.size();
465     }
466
467     /**
468      * Returns the number of items in all series. This method is defined by
469      * the {@link TableXYDataset} interface.
470      *
471      * @return The item count.
472      */

473     public int getItemCount() {
474         return getItemCount(0);
475     }
476     
477     /**
478      * Returns the number of series in the dataset.
479      *
480      * @return The seriesCount value
481      *
482      * @see XYDataset
483      * @see Dataset
484      */

485     public int getSeriesCount() {
486         return this.columnNames.length;
487     }
488
489     /**
490      * Returns the key for the specified series.
491      *
492      * @param seriesIndex the series (zero-based index).
493      *
494      * @return The seriesName value
495      *
496      * @see XYDataset
497      * @see Dataset
498      */

499     public Comparable JavaDoc getSeriesKey(int seriesIndex) {
500
501         if ((seriesIndex < this.columnNames.length)
502                 && (this.columnNames[seriesIndex] != null)) {
503             return this.columnNames[seriesIndex];
504         }
505         else {
506             return "";
507         }
508
509     }
510
511     /**
512      * Returns the number of items that should be displayed in the legend.
513      *
514      * @return The legendItemCount value
515      *
516      * @deprecated This method is not used in JFreeChart 1.0.x (it was left in
517      * the API by mistake and is officially deprecated from version 1.0.3
518      * onwards).
519      */

520     public int getLegendItemCount() {
521         return getSeriesCount();
522     }
523
524     /**
525      * Returns the legend item labels.
526      *
527      * @return The legend item labels.
528      *
529      * @deprecated This method is not used in JFreeChart 1.0.x (it was left in
530      * the API by mistake and is officially deprecated from version 1.0.3
531      * onwards).
532      */

533     public String JavaDoc[] getLegendItemLabels() {
534         return this.columnNames;
535     }
536
537     /**
538      * Close the database connection
539      */

540     public void close() {
541
542         try {
543             this.connection.close();
544         }
545         catch (Exception JavaDoc e) {
546             System.err.println("JdbcXYDataset: swallowing exception.");
547         }
548
549     }
550
551     /**
552      * Returns the minimum y-value in the dataset.
553      *
554      * @param includeInterval a flag that determines whether or not the
555      * y-interval is taken into account.
556      *
557      * @return The minimum value.
558      */

559     public double getRangeLowerBound(boolean includeInterval) {
560         return this.minValue;
561     }
562     
563     /**
564      * Returns the maximum y-value in the dataset.
565      *
566      * @param includeInterval a flag that determines whether or not the
567      * y-interval is taken into account.
568      *
569      * @return The maximum value.
570      */

571     public double getRangeUpperBound(boolean includeInterval) {
572         return this.maxValue;
573     }
574
575     /**
576      * Returns the range of the values in this dataset's range.
577      *
578      * @param includeInterval a flag that determines whether or not the
579      * y-interval is taken into account.
580      *
581      * @return The range.
582      */

583     public Range getRangeBounds(boolean includeInterval) {
584         return new Range(this.minValue, this.maxValue);
585     }
586
587 }
588
Popular Tags