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