001    /**
002     * ========================================
003     * JFreeReport : a free Java report library
004     * ========================================
005     *
006     * Project Info:  http://reporting.pentaho.org/
007     *
008     * (C) Copyright 2000-2007, by Object Refinery Limited, Pentaho Corporation and Contributors.
009     *
010     * This library is free software; you can redistribute it and/or modify it under the terms
011     * of the GNU Lesser General Public License as published by the Free Software Foundation;
012     * either version 2.1 of the License, or (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015     * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016     * See the GNU Lesser General Public License for more details.
017     *
018     * You should have received a copy of the GNU Lesser General Public License along with this
019     * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020     * Boston, MA 02111-1307, USA.
021     *
022     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023     * in the United States and other countries.]
024     *
025     * ------------
026     * $Id: ScrollableResultSetTableModel.java 2725 2007-04-01 18:49:29Z taqua $
027     * ------------
028     * (C) Copyright 2000-2005, by Object Refinery Limited.
029     * (C) Copyright 2005-2007, by Pentaho Corporation.
030     */
031    package org.jfree.report.modules.misc.tablemodel;
032    
033    import java.sql.ResultSet;
034    import java.sql.ResultSetMetaData;
035    import java.sql.SQLException;
036    import javax.swing.table.AbstractTableModel;
037    
038    import org.jfree.util.Log;
039    
040    /**
041     * A tableModel which is backed up by a java.sql.ResultSet. Use this to directly feed your
042     * database data into JFreeReport. If you have trouble using this TableModel and you have
043     * either enough memory or your query result is not huge, you may want to use
044     * <code>ResultSetTableModelFactory.generateDefaultTableModel (ResultSet rs)</code>. That
045     * implementation will read all data from the given ResultSet and keep that data in
046     * memory.
047     * <p/>
048     * Use the close() function to close the ResultSet contained in this model.
049     *
050     * @author Thomas Morgner
051     */
052    public class ScrollableResultSetTableModel extends AbstractTableModel
053            implements CloseableTableModel
054    {
055      /**
056       * The scrollable ResultSet source.
057       */
058      private ResultSet resultset;
059      /**
060       * The ResultSetMetaData object for this result set.
061       */
062      private ResultSetMetaData dbmd;
063      /**
064       * The number of rows in the result set.
065       */
066      private int rowCount;
067      /**
068       * Defines the column naming mode.
069       */
070      private final boolean labelMapMode;
071      /**
072       * The column types as read from the result set.
073       */
074      private Class[] types;
075    
076      /**
077       * Constructs the model.
078       *
079       * @param resultset    the result set.
080       * @param labelMapMode defines, whether to use column names or column labels to compute
081       *                     the column index.
082       * @throws SQLException if there is a problem with the result set.
083       */
084      public ScrollableResultSetTableModel (final ResultSet resultset,
085                                            final boolean labelMapMode)
086              throws SQLException
087      {
088        this.labelMapMode = labelMapMode;
089        if (resultset != null)
090        {
091          updateResultSet(resultset);
092        }
093        else
094        {
095          close();
096        }
097      }
098    
099      /**
100       * Creates a new scrollable result set with no resultset assigned and the specified
101       * label map mode.
102       *
103       * @param labelMapMode defines, whether to use column names or column labels to compute
104       *                     the column index.
105       */
106      protected ScrollableResultSetTableModel (final boolean labelMapMode)
107      {
108        this.labelMapMode = labelMapMode;
109      }
110    
111      /**
112       * Returns the column name mode used to map column names into column indices. If true,
113       * then the Label is used, else the Name is used.
114       *
115       * @return true, if the column label is used for the mapping, false otherwise.
116       *
117       * @see ResultSetMetaData#getColumnLabel
118       * @see ResultSetMetaData#getColumnName
119       */
120      public boolean isLabelMapMode ()
121      {
122        return labelMapMode;
123      }
124    
125      /**
126       * Updates the result set in this model with the given ResultSet object.
127       *
128       * @param resultset the new result set.
129       * @throws SQLException if there is a problem with the result set.
130       */
131      public void updateResultSet (final ResultSet resultset)
132              throws SQLException
133      {
134        if (this.resultset != null)
135        {
136          close();
137        }
138    
139        this.resultset = resultset;
140        this.dbmd = resultset.getMetaData();
141    
142        if (resultset.last())
143        {
144          rowCount = resultset.getRow();
145        }
146        else
147        {
148          rowCount = 0;
149        }
150    
151        fireTableStructureChanged();
152      }
153    
154      /**
155       * Clears the model of the current result set. The resultset is closed.
156       */
157      public void close ()
158      {
159        // Close the old result set if needed.
160        if (resultset != null)
161        {
162          try
163          {
164            resultset.close();
165          }
166          catch (SQLException e)
167          {
168            // Just in case the JDBC driver can't close a result set twice.
169            //  e.printStackTrace();
170          }
171        }
172        resultset = null;
173        dbmd = null;
174        rowCount = 0;
175        fireTableStructureChanged();
176      }
177    
178      /**
179       * Get a rowCount. This can be a very expensive operation on large datasets. Returns -1
180       * if the total amount of rows is not known to the result set.
181       *
182       * @return the row count.
183       */
184      public int getRowCount ()
185      {
186        if (resultset == null)
187        {
188          return 0;
189        }
190    
191        try
192        {
193          if (resultset.last())
194          {
195            rowCount = resultset.getRow();
196            if (rowCount == -1)
197            {
198              rowCount = 0;
199            }
200          }
201          else
202          {
203            rowCount = 0;
204          }
205        }
206        catch (SQLException sqle)
207        {
208          //Log.debug ("GetRowCount failed, returning 0 rows", sqle);
209          return 0;
210        }
211        return rowCount;
212      }
213    
214      /**
215       * Returns the number of columns in the ResultSet. Returns 0 if no result set is set or
216       * the column count could not be retrieved.
217       *
218       * @return the column count.
219       *
220       * @see java.sql.ResultSetMetaData#getColumnCount()
221       */
222      public int getColumnCount ()
223      {
224        if (resultset == null)
225        {
226          return 0;
227        }
228    
229        if (dbmd != null)
230        {
231          try
232          {
233            return dbmd.getColumnCount();
234          }
235          catch (SQLException e)
236          {
237            //Log.debug ("GetColumnCount failed", e);
238          }
239        }
240        return 0;
241      }
242    
243      /**
244       * Returns the columnLabel or column name for the given column. Whether the label or the
245       * name is returned depends on the label map mode.
246       *
247       * @param column the column index.
248       * @return the column name.
249       *
250       * @see java.sql.ResultSetMetaData#getColumnLabel(int)
251       */
252      public String getColumnName (final int column)
253      {
254        if (dbmd != null)
255        {
256          try
257          {
258            if (isLabelMapMode())
259            {
260              return dbmd.getColumnLabel(column + 1);
261            }
262            else
263            {
264              return dbmd.getColumnName(column + 1);
265            }
266          }
267          catch (SQLException e)
268          {
269            Log.info("ScrollableResultSetTableModel.getColumnName: SQLException.");
270          }
271        }
272        return null;
273      }
274    
275      /**
276       * Returns the value of the specified row and the specified column from within the
277       * resultset.
278       *
279       * @param row    the row index.
280       * @param column the column index.
281       * @return the value.
282       */
283      public Object getValueAt (final int row, final int column)
284      {
285        if (resultset != null)
286        {
287          try
288          {
289            resultset.absolute(row + 1);
290            return resultset.getObject(column + 1);
291          }
292          catch (SQLException e)
293          {
294            //Log.debug ("Query failed for [" + row + "," + column + "]", e);
295          }
296        }
297        return null;
298      }
299    
300      /**
301       * Returns the class of the resultset column. Returns Object.class if an error
302       * occurred.
303       *
304       * @param column the column index.
305       * @return the column class.
306       */
307      public Class getColumnClass (final int column)
308      {
309        if (types != null)
310        {
311          return types[column];
312        }
313        if (dbmd != null)
314        {
315          try
316          {
317            types = TypeMapper.mapTypes(dbmd);
318            return types[column];
319          }
320          catch (Exception e)
321          {
322            //Log.debug ("GetColumnClass failed for " + column, e);
323          }
324        }
325        return Object.class;
326      }
327    
328    
329      /**
330       * Returns the classname of the resultset column. Returns Object.class if an error
331       * occurred.
332       *
333       * @param column the column index.
334       * @return the column class name.
335       */
336      public String getColumnClassName (final int column)
337      {
338        if (dbmd != null)
339        {
340          return mckoiDBFixClassName(getColumnClass(column).getName());
341        }
342        return Object.class.getName();
343      }
344    
345      /**
346       * Just removes the word class from the start of the classname string McKoiDB version
347       * 0.92 was not able to properly return classnames of resultset elements.
348       *
349       * @param classname the class name.
350       * @return the modified class name.
351       */
352      private String mckoiDBFixClassName (final String classname)
353      {
354        if (classname.startsWith("class "))
355        {
356          return classname.substring(6).trim();
357        }
358        return classname;
359      }
360    }