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.math.BigDecimal;
024 import java.sql.Blob;
025 import java.sql.Clob;
026 import java.sql.Date;
027 import java.sql.Ref;
028 import java.sql.ResultSet;
029 import static java.sql.ResultSet.CONCUR_UPDATABLE;
030 import java.sql.ResultSetMetaData;
031 import java.sql.SQLException;
032 import java.sql.Struct;
033 import java.sql.Time;
034 import java.sql.Timestamp;
035 import java.sql.Types;
036 import javax.sql.RowSet;
037 import javax.sql.RowSetEvent;
038 import javax.sql.RowSetListener;
039 import javax.swing.JTable;
040 import javax.swing.table.AbstractTableModel;
041 import javax.swing.table.TableModel;
042 import org.jetbrains.annotations.Nullable;
043 import net.sf.japi.util.ThrowableHandler;
044
045 /** An implementation of <code>javax.swing.TableModel</code> for an SQL ResultSet.
046 * It is required that the ResultSet is absolutely navigatable.
047 * That feature heavily depends on the JDBC Driver implementation.
048 * Please note that though this class does NOT store the ResultSet data except some meta data, the {@link JTable} probably will in its own private shadow copy table model.
049 * @see ResultSet
050 * @see TableModel
051 * @author <a href="mailto:chris@riedquat.de">Christian Hujer</a>
052 */
053 public class ScrollResultSetTableModel extends AbstractTableModel implements ResultSetTableModel, RowSetListener {
054
055 /** Serial Version. */
056 private static final long serialVersionUID = 1L;
057
058 /** The ResultSet. */
059 @Nullable private transient ResultSet resultSet;
060
061 /** The ResultSetMetaData. */
062 @Nullable private transient ResultSetMetaData metaData;
063
064 /** The number of rows.
065 * @serial include
066 */
067 private int rowCount;
068
069 /** The number of columns.
070 * @serial include
071 */
072 private int columnCount;
073
074 /** The column titles.
075 * @serial include
076 */
077 private String[] columnTitles;
078
079 /** Create a ResultSetTableModel. */
080 public ScrollResultSetTableModel() {
081 }
082
083 /** Create a ResultSetTableModel.
084 * @param rs Initial ResultSet
085 */
086 public ScrollResultSetTableModel(final ResultSet rs) {
087 setResultSet(rs);
088 }
089
090 /** {@inheritDoc} */
091 public void setResultSet(final ResultSet resultSet) {
092 this.resultSet = resultSet;
093 updateResultSetData();
094 }
095
096 /** Report an exception to all registered ThrowableHandlers.
097 * @param exception Exception to report
098 */
099 private void handleException(final SQLException exception) {
100 final Object[] listeners = listenerList.getListenerList();
101 for (int i = listeners.length - 2; i >= 0; i -= 2) {
102 //noinspection ObjectEquality
103 if (listeners[i] == ThrowableHandler.class) {
104 ((ThrowableHandler<? super SQLException>)listeners[i+1]).handleThrowable(exception);
105 }
106 }
107 }
108
109 /** {@inheritDoc} */
110 public ResultSet getResultSet() {
111 return resultSet;
112 }
113
114 /** {@inheritDoc} */
115 public void addThrowableHandler(final ThrowableHandler<? super SQLException> throwableHandler) {
116 listenerList.add(ThrowableHandler.class, throwableHandler);
117 }
118
119 /** {@inheritDoc} */
120 public void removeThrowableHandler(final ThrowableHandler<? super SQLException> throwableHandler) {
121 listenerList.remove(ThrowableHandler.class, throwableHandler);
122 }
123
124 /** {@inheritDoc} */
125 public void rowSetChanged(final RowSetEvent event) {
126 System.err.println(event);
127 updateResultSetData();
128 }
129
130 /** {@inheritDoc} */
131 // Remove ConstantConditions once IntelliJ IDEA is smarter about null
132 @SuppressWarnings({"ConstantConditions"})
133 public void rowChanged(final RowSetEvent event) {
134 System.err.println(event);
135 if (resultSet == null) {
136 return;
137 }
138 try {
139 final int row = resultSet.getRow();
140 if (resultSet.rowDeleted()) {
141 fireTableRowsDeleted(row, row);
142 } else if (resultSet.rowInserted()) {
143 fireTableRowsInserted(row, row);
144 } else if (resultSet.rowUpdated()) {
145 fireTableRowsUpdated(row, row);
146 }
147 } catch (final SQLException e) {
148 handleException(e);
149 }
150 }
151
152 /** {@inheritDoc} */
153 public void cursorMoved(final RowSetEvent event) {
154 System.err.println(event);
155 // ignore
156 }
157
158 /** {@inheritDoc} */
159 public int getRowCount() {
160 if (resultSet == null) {
161 return 0;
162 }
163 try {
164 if (resultSet.last()) {
165 return resultSet.getRow();
166 } else {
167 return 0;
168 }
169 } catch (final SQLException e) {
170 handleException(e);
171 return 0;
172 }
173 //return rowCount;
174 }
175
176 /** {@inheritDoc} */
177 public int getColumnCount() {
178 if (metaData == null) {
179 return 0;
180 }
181 try {
182 return metaData.getColumnCount();
183 } catch (final SQLException e) {
184 handleException(e);
185 return 0;
186 }
187 //return columnCount;
188 }
189
190 /** {@inheritDoc} */
191 // Remove ConstantConditions once IntelliJ IDEA is smarter about null
192 @SuppressWarnings({"ConstantConditions"})
193 @Nullable public Object getValueAt(final int rowIndex, final int columnIndex) {
194 if (resultSet == null) {
195 return null;
196 }
197 try {
198 resultSet.absolute(rowIndex + 1);
199 return resultSet.getString(columnIndex + 1);
200 } catch (final SQLException e) {
201 handleException(e);
202 setResultSet(null);
203 return null;
204 }
205 }
206
207 /** {@inheritDoc} */
208 @SuppressWarnings({"OverlyComplexMethod", "OverlyLongMethod", "SwitchStatementWithTooManyBranches"})
209 @Override public Class<?> getColumnClass(final int columnIndex) {
210 final int type;
211 try {
212 if (metaData == null) {
213 // TODO: Display error to usage, reset state
214 return super.getColumnClass(columnIndex);
215 }
216 type = metaData.getColumnType(columnIndex + 1);
217 } catch (final SQLException e) {
218 handleException(e);
219 // TODO: reset state
220 return super.getColumnClass(columnIndex);
221 }
222 switch (type) {
223 case Types.BIT: return Boolean.class;
224 case Types.TINYINT: return Byte.class;
225 case Types.SMALLINT: return Short.class;
226 case Types.INTEGER: return Integer.class;
227 case Types.BIGINT: return Long.class;
228 case Types.FLOAT:
229 case Types.REAL: return Float.class;
230 case Types.DOUBLE: return Double.class;
231 case Types.NUMERIC: return Number.class;
232 case Types.DECIMAL: return BigDecimal.class;
233 case Types.CHAR:
234 case Types.VARCHAR:
235 case Types.LONGVARCHAR: return String.class;
236 case Types.DATE: return Date.class;
237 case Types.TIME: return Time.class;
238 case Types.TIMESTAMP: return Timestamp.class;
239 case Types.BINARY:
240 case Types.VARBINARY:
241 case Types.LONGVARBINARY: return byte[].class;
242 case Types.OTHER:
243 case Types.JAVA_OBJECT: return Object.class;
244 case Types.CLOB: return Clob.class;
245 case Types.BLOB: return Blob.class;
246 case Types.REF: return Ref.class;
247 case Types.STRUCT: return Struct.class;
248 default: return super.getColumnClass(columnIndex);
249 }
250 }
251
252 /** {@inheritDoc} */
253 @Override @Nullable public String getColumnName(final int column) {
254 if (metaData == null) {
255 // TODO
256 return null;
257 }
258 try {
259 return metaData.getColumnName(column + 1);
260 } catch (final SQLException e) {
261 handleException(e);
262 return null;
263 }
264 //return columnTitles[column];
265 }
266
267 /** Insert a row.
268 * This is a proxy method to the underlying result set.
269 * @param rowData data for new row
270 * @throws SQLException in case of SQL problems
271 */
272 // Remove ConstantConditions once IntelliJ IDEA is smarter about null
273 @SuppressWarnings({"ConstantConditions"})
274 public void insert(final String[] rowData) throws SQLException {
275 if (resultSet != null) {
276 return;
277 }
278 resultSet.moveToInsertRow();
279 for (int i = 0; i < rowData.length; i++) {
280 resultSet.updateString(i + 1, rowData[i]);
281 }
282 resultSet.insertRow();
283 resultSet.moveToCurrentRow();
284 final int row = resultSet.getRow();
285 fireTableRowsInserted(row, row);
286 }
287
288 /** {@inheritDoc} */
289 @Override public boolean isCellEditable(final int rowIndex, final int columnIndex) {
290 try {
291 return resultSet != null && resultSet.getConcurrency() == CONCUR_UPDATABLE;
292 } catch (final SQLException e) {
293 handleException(e);
294 // TODO: reset state
295 return false;
296 }
297 }
298
299 /** {@inheritDoc} */
300 // Remove ConstantConditions once IntelliJ IDEA is smarter about null
301 @SuppressWarnings({"ConstantConditions"})
302 @Override public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) {
303 if (resultSet == null) {
304 return;
305 }
306 try {
307 if (!resultSet.absolute(rowIndex + 1)) {
308 // TODO: Display error to usage, reset state
309 return;
310 }
311 resultSet.updateObject(columnIndex + 1, aValue);
312 resultSet.updateRow();
313 } catch (final SQLException e) {
314 handleException(e);
315 // TODO: reset state
316 }
317 }
318
319 /** Updates the data from the result set. */
320 private void updateResultSetData() {
321 if (resultSet == null) {
322 rowCount = 0;
323 columnCount = 0;
324 columnTitles = null;
325 } else {
326 try {
327 metaData = resultSet.getMetaData();
328 if (resultSet instanceof RowSet) {
329 ((RowSet) resultSet).addRowSetListener(this);
330 }
331 columnTitles = SQLHelper.getColumnLabels(resultSet);
332 columnCount = columnTitles.length;
333 rowCount = SQLHelper.getRowCount(resultSet);
334 } catch (final SQLException e) {
335 handleException(e);
336 resultSet = null;
337 rowCount = 0;
338 columnCount = 0;
339 columnTitles = null;
340 }
341 }
342 fireTableStructureChanged();
343 }
344
345 /** Delete a row.
346 * This is a proxy method to the underlying result set.
347 * @param n row number to delete (Java row number, 0 .. (number of rows - 1))
348 * @throws SQLException in case of SQL problems
349 */
350 // Remove ConstantConditions once IntelliJ IDEA is smarter about null
351 @SuppressWarnings({"ConstantConditions"})
352 public void deleteRow(final int n) throws SQLException {
353 if (resultSet == null) {
354 return;
355 }
356 resultSet.absolute(n + 1);
357 resultSet.deleteRow();
358 fireTableRowsDeleted(n, n);
359 }
360
361 /** Get the meta data.
362 * @return meta data
363 */
364 public ResultSetMetaData getMetaData() {
365 return metaData;
366 }
367
368 } // class ResultSetTableModel