001 /* JAPI - (Yet another (hopefully) useful) Java API
002 *
003 * Copyright (C) 2004-2006 Christian Hujer
004 *
005 * This program is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU General Public License as
007 * published by the Free Software Foundation; either version 2 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful, but
011 * WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013 * General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program; if not, write to the Free Software
017 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018 * 02111-1307, USA.
019 */
020
021 package net.sf.japi.sql;
022
023 import java.sql.ResultSet;
024 import java.sql.SQLException;
025 import javax.swing.table.AbstractTableModel;
026 import javax.swing.table.TableModel;
027 import org.jetbrains.annotations.Nullable;
028 import net.sf.japi.util.ThrowableHandler;
029
030 /** An implementation of <code>javax.swing.TableModel</code> for an SQL ResultSet.
031 * In contrast to @see ResultSetTableModel this implementation reads all data upon setting the ResultSet.
032 * The advantage is that reading the ResultSet does not require the database connection anymore.
033 * The disadvantage is that this class requires much memory on large results and may fail for OutOfMemory on huge results.
034 * This class is fully serializable, at least in the way Swing classes are at all serializable.
035 * <p/>
036 * Serialized instances will also serialize the cached data.
037 * The data source information is not serialized.
038 * @see ResultSet
039 * @see TableModel
040 * @author <a href="mailto:chris@riedquat.de">Christian Hujer</a>
041 * @todo maybe setResultSet should throw SQLException?
042 */
043 public class CachedResultSetTableModel extends AbstractTableModel implements ResultSetTableModel {
044
045 /** Serial Version. */
046 private static final long serialVersionUID = 1L;
047
048 /** The number of rows.
049 * @serial include
050 */
051 private int rowCount;
052
053 /** The number of columns.
054 * @serial include
055 */
056 private int columnCount;
057
058 /** The column titles.
059 * @serial include
060 */
061 private String[] columnTitles;
062
063 /** The Data.
064 * @serial include
065 */
066 private Object[][] data;
067
068 /** The ResultSet. */
069 private ResultSet resultSet;
070
071 /** Create a CachedResultSetTableModel. */
072 public CachedResultSetTableModel() {
073 }
074
075 /** Create a CachedResultSetTableModel.
076 * @param rs Initial ResultSet
077 */
078 public CachedResultSetTableModel(final ResultSet rs) {
079 setResultSet(rs);
080 }
081
082 /** Report an exception to all registered ThrowableHandlers.
083 * @param exception Exception to report
084 */
085 private void handleException(final SQLException exception) {
086 final Object[] listeners = listenerList.getListenerList();
087 for (int i = listeners.length - 2; i >= 0; i -= 2) {
088 //noinspection ObjectEquality
089 if (listeners[i] == ThrowableHandler.class) {
090 ((ThrowableHandler<? super SQLException>)listeners[i+1]).handleThrowable(exception);
091 }
092 }
093 }
094
095 /** {@inheritDoc} */
096 public void setResultSet(final ResultSet resultSet) {
097 if (resultSet == null) {
098 this.resultSet = resultSet;
099 rowCount = 0;
100 columnCount = 0;
101 columnTitles = null;
102 data = null;
103 } else {
104 try {
105 columnTitles = SQLHelper.getColumnLabels(resultSet);
106 columnCount = columnTitles.length;
107 rowCount = SQLHelper.getRowCount(resultSet);
108 data = SQLHelper.getData(resultSet);
109 this.resultSet = resultSet;
110 } catch (final SQLException e) {
111 handleException(e);
112 rowCount = 0;
113 columnCount = 0;
114 columnTitles = null;
115 data = null;
116 return; // don't invoke fireTableStructureChanged() twice then.
117 }
118 }
119 fireTableStructureChanged();
120 }
121
122 /** {@inheritDoc} */
123 public ResultSet getResultSet() {
124 return resultSet;
125 }
126
127 /** {@inheritDoc} */
128 public void addThrowableHandler(final ThrowableHandler<? super SQLException> throwableHandler) {
129 listenerList.add(ThrowableHandler.class, throwableHandler);
130 }
131
132 /** {@inheritDoc} */
133 public void removeThrowableHandler(final ThrowableHandler<? super SQLException> throwableHandler) {
134 listenerList.remove(ThrowableHandler.class, throwableHandler);
135 }
136
137 /** @see TableModel */
138 public int getColumnCount() {
139 return columnCount;
140 }
141
142 /** @see TableModel */
143 public int getRowCount() {
144 return rowCount;
145 }
146
147 /** @see TableModel */
148 @Nullable public Object getValueAt(final int rowIndex, final int columnIndex) {
149 try {
150 return data[rowIndex][columnIndex];
151 } catch (final NullPointerException e) {
152 return null;
153 } catch (final ArrayIndexOutOfBoundsException e) {
154 return null;
155 }
156 }
157
158 /** {@inheritDoc}
159 * Always returns <code>String.class</code>.
160 */
161 @Override public Class<?> getColumnClass(final int columnIndex) {
162 return String.class;
163 }
164
165 /** {@inheritDoc} */
166 @Override public String getColumnName(final int column) {
167 return columnTitles[column];
168 }
169
170 } // class CachedResultSetTableModel