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