RSIterator.java
/***************************************************************************
Copyright 2012 Emily Estes
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
***************************************************************************/
package net.metanotion.sql;
import java.io.Closeable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import net.metanotion.util.types.Parser;
/** An implementation of {@link java.util.Iterator} that wraps a {@link java.sql.ResultSet} and uses an instance of
{@link net.metanotion.util.types.Parser} to interpret the rows of the result set into "value objects" produced by the
iterator.
@param <S> The type this iterator produces from ResultSet rows.
*/
public final class RSIterator<S> implements Iterator<S>, Closeable {
/** The result set that backs this iterator. */
private final ResultSet rs;
/** The getter which knows how to interpret the rows of the result set. */
private final Parser<S> g;
/** Since the only way to find out if a result set has more results(in general) is to call it's ".next()" method,
this variable is used to cache the value encountered when discovering this fact. */
private S s = null;
/** If the ResultSet has any more values to produce, this flag will be false. Otherwise, it will be
true and we should return false from ".hasNext()" and throw a NoSuchElement exception on ".next()". */
private boolean finished = false;
/** Construct a new iterator backed by the result set and interpreted by the getter.
@param rs The result set that backs this iteartor instance.
@param g The getter instance which knows how to convert the rows of the result set into values of the
proper type.
*/
public RSIterator(ResultSet rs, Parser<S> g) {
this.rs = rs;
this.g = g;
}
/** Load the next value from the result set.
@return Attempt to load the next value from the result set. If the result set has
been previously marked as finished, we return null. Otherwise, we call the
ResultSet.next method. If THAT returns false, we mark this iterator as
finished and close the result set, and return null. If there IS a row, we
interpret it with the getter and return that value.
@throws RuntimeExceptions if the result set or the getter produce one.
*/
private S load() {
try {
if(this.finished) { return null; }
try {
if(!rs.next()) {
this.finished = true;
try{
rs.close();
} catch (final SQLException sqle) { throw new RuntimeException(sqle); }
return null;
}
} catch (final SQLException e) {
// rs.next threw an excpetion. Avoid a resource leak.
this.finished = true;
try{
rs.close();
} catch (final SQLException sqle) { throw new RuntimeException(sqle); }
throw e;
}
return g.parse(rs);
} catch (final Exception e) { throw new RuntimeException(e); }
}
/** Determine if there are more results in the result set.
Basically, we look to see if there is a cached value. If there is we
return true. If there isn't, we call the private internal method .load() to find if there is. If that
returns null, we return false, otherwise, true.
@return true if there are more results, false otherwise.
*/
@Override public boolean hasNext() {
if(this.s == null) {
this.s = this.load();
return (this.s != null);
} else {
return true;
}
}
/** Return the next value from the result set.
If there is a cached value, set the cache to null and return the cached value as the result.
Otherwise, we call the private internal method .load() to produce another value. If that fails, the result set is
finished(and closed) and we throw a NoSuchElementException.
@return an instance of S containing the row of the result set.
@throws NoSuchElementException if there are no more results.
*/
@Override public S next() {
if(s != null) {
final S s2 = s;
s = null;
return s2;
} else {
final S s2 = load();
if(s2 == null) { throw new NoSuchElementException(); }
return s2;
}
}
/** While there are result sets that can delete rows, we currently do not attempt to support that behavior,
so we throw and UnsupportedOperationException.
@throws UnsupportedOperationException always. */
@Override public void remove() { throw new UnsupportedOperationException(); }
/** Close the result set backing this object. This is idempotent and safe to call multiple times. Also
if the result set has been exhausted by iteration it is automatically closed. */
@Override public void close() {
if(this.finished) { return; }
this.finished = true;
try {
rs.close();
} catch (SQLException se) {
throw new RuntimeException(se);
}
}
}