Cargar un ResultSet en un JTable

Hace unos días se me presentó la necesidad de mostrar los resultados de una consulta en un JTable, en todas partes la solución propuesta consiste en implementar la interfaz TableModel y cargar el ResultSet en una Array de dos dimensiones. El problema con este método es determinar el número de filas del ResultSet para lo cual encontré este interesante método:

try
{
	rs.last(); //Muevo el cursor al último registro del ResultSet
	int noFilas = rs.getRow(); //Obtengo el número de la fila actual
}
catch(SQLException ex)
{
	System.out.println(ex.toString());
}

Y eso fue todo lo que necesité. Sin embargo al intentar hacer mi programa más portable, decidí no usar MySQL sino una base de datos embebida, escogí SQLite, inmediatamente el código dejó de funcionar y obtenía la siguiente excepción:

java.sql.SQLException: ResultSet is TYPE_FORWARD_ONLY

Lo que es claro, no todas los controladores de bases de datos soportan las instrucciones last() y beforeFirst() y tenemos que conformarnos con next(). La solución entonces no era universal y tenía que crear una, mi propuesta es la siguiente clase que utiliza ArrayList.

import java.sql.*;
import java.sql.*;
import java.util.ArrayList;
import javax.swing.event.*;

public class ResultSetTable implements javax.swing.table.TableModel
{

    private ArrayList<ArrayList<Object>> Datos; //Datos
    private String[] etiquetas; //Nombres de columna
    protected EventListenerList listenerList = new EventListenerList();

    public ResultSetTable(java.sql.ResultSet rs)
    {
        if (rs != null)
        {
            Datos = new ArrayList();
            try
            {
                ResultSetMetaData metaDatos = rs.getMetaData();
                int noColumnas = metaDatos.getColumnCount();
                etiquetas = new String[noColumnas];
                for (int i = 0; i < noColumnas; i++)
                {
                    etiquetas[i] = metaDatos.getColumnLabel(i + 1);
                }
                ArrayList<Object> temp;
                while (rs.next())
                {
                    temp = new ArrayList();
                    for (String etiqueta : etiquetas)
                    {
                        temp.add(rs.getObject(etiqueta));
                    }
                    Datos.add(temp);
                }
            }
            catch (SQLException ex)
            {
                Datos = null;
                etiquetas = null;
                System.out.println("Error: " + ex.getErrorCode());
                System.out.println("Mensaje: " + ex.getMessage());
            }
        }
    }

    public int getRowCount()
    {
        return Datos == null ? 0 : Datos.size();
    }

    public int getColumnCount()
    {
        return Datos == null ? 0 : Datos.get(0).size();
    }

    public Object getValueAt(int rowIndex, int columnIndex)
    {
        return Datos == null ? null : Datos.get(rowIndex).get(columnIndex);
    }

    public String getColumnName(int columnIndex)
    {
        return etiquetas == null ? null : etiquetas[columnIndex];
    }

    public Class<?> getColumnClass(int columnIndex)
    {
        return Object.class;
    }

    public boolean isCellEditable(int rowIndex, int columnIndex)
    {
        return false;
    }

    public void setValueAt(Object aValue, int rowIndex, int columnIndex)
    {
        Datos.get(rowIndex).set(columnIndex, aValue);
    }

    public void addTableModelListener(TableModelListener l) {
		listenerList.add(TableModelListener.class, l);
    }

    public void removeTableModelListener(TableModelListener l) {
		listenerList.remove(TableModelListener.class, l);
    }
}

Ahora basta con poner la clase en el mismo paquete que nuestro programa y

JTable tabla = new JTable();
tabla.setModel(new ResultSetTable(rs)); //rs es un ResultSet previamente cargado