package com.databasesandlife;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;

/**
 * Takes a {@link ResultSet} and creates an {@link Iterator} interface that returns the results.
 * <p>
 * Subclasses may be created which convert a particular row into a particular type of object
 * to be returned from the iterator.
 * <p>
 * It is intended for those ResultSets which can only iterate forward (i.e. streaming results).
 * On these ResultSets the <code>aResultSet.hasNext</code>
 * method cannot be called.
 * Therefore the ResultSet only has a <code>next</code> method,
 * but the Iterator must provide a <code>hasNext</code>
 * method which can be called an arbitrary number of times to see if there would be a next row.
 * The algorithm employed is to always fetch one row ahead store it in the object, so <code>hasNext</code>
 * can return if there is a next row or not. The <code>next</code> method then returns that previously fetched one, and
 * fetches the next one.
 * <p>
 * Any {@link SQLException} which are thrown from the ResultSet are assumed to be "should never happen"
 * type, and thus are transformed into {@link RuntimeException}s.
 * <p>
 * Usage:
 * <pre>
 * public class MyDocResultSetIterator extends ResultSetIterator&lt;MyDoc> {
 *     protected MyDoc objectForRow(ResultSet r) throws SQLException {
 *         MyDoc result = new MyDoc();
 *         result.setVal(r.getString("val"));
 *         return result;
 *     }
 * }
 * public Iterator&lt;MyDoc> foo() {
 *     PreparedStatement stat = aConnection.prepareStatement(
 *         "SELECT val FROM big_table",
 *         ResultSet.TYPE_FORWARD_ONLY,
 *         ResultSet.CONCUR_READ_ONLY);
 *     stat.setFetchSize(Integer.MIN_VALUE);
 *     ResultSet results = stat.executeQuery();
 *     return new MyDocResultSetIterator(results);
 * }
 * </pre>
 * This code is released under the <a href="http://www.gnu.org/licenses/lgpl.html">LGPL</a>.
 * It is tested using Java 6 with MySQL 5.0 and the JDBC driver "MySQL Connector" 5.1.15.
 * 
 * @see <a href="http://www.databasesandlife.com/reading-row-by-row-into-java-from-mysql/">Reading row-by-row into Java from MySQL</a>
 * @author Adrian Smith &lt;adrian.m.smith@gmail.com&gt;
 * @version $Revision: 810 $
 */
public abstract class ResultSetIterator<T> implements Iterator<T> {
    
    private ResultSet resultSet;
    private T nextObject;
    
    protected ResultSetIterator(ResultSet resultSet) {
        this.resultSet = resultSet;
        next(); // get the 1st row into nextObject
    }
    
    /**
     * Looks in the current data in the resultSet and
     * returns a new object to be returned from the Iterator.
     * @param r cursor is already at the row containing the data.
     * @return new object representing the data in the ResultSet.
     * @throws SQLException if there is some problem reading the ResultSet.
     *         (This is converted into a RuntimeException by {@link #next}, so there is
     *          no point implementing the same code in the subclass' implementation of this method.)
     */
    protected abstract T newObjectForRow(ResultSet r) throws SQLException;

    public boolean hasNext() {
        return nextObject != null;
    }

    public T next() {
        try {
            T result = nextObject;
            nextObject = resultSet.next() ? newObjectForRow(resultSet) : null;
            return result;
        }
        catch (SQLException e) { throw new RuntimeException(e); }
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }
}
