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