package com.moparisthebest.jdbc.util;
import java.io.Closeable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.Iterator;
import java.util.NoSuchElementException;
//IFJAVA8_START
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
//IFJAVA8_END
import static com.moparisthebest.jdbc.TryClose.tryClose;
/**
* Iterable/Iterator interface over a ResultSet
*
* This has some special semantics the caller must be aware of though:
*
* 1. It has to be closed in a finally just like a ResultSet so it can close it's underlying ResultSet
* 2. The .iterator() implementation just returns this, meaning you can only loop over it once, if you need to loop multiple times get a List or something
* 3. This guarantees that the ResultSet/Calendar you send into this class with the ResultSetToObject will be the only
* instances sent into the ResultSetToObject.toObject() method, so if you do fancy initialization of some sort you can
* do it in the constructor based on those instances, or once on the first call to .toObject() and cache it forever.
* Or it can be entirely stateless and re-used across multiple ResultSets, your choice.
* 4. If you set a PreparedStatement to close, you can only set it once, and it will close it when you call .close() on this
*/
public class ResultSetIterable implements Iterable, Iterator, Closeable {
public static final ResultSetIterable EMPTY_RESULT_SET_ITERABLE = new EmptyResultSetIterable();
@SuppressWarnings("unchecked")
public static ResultSetIterable emptyResultSetIterable() {
return (ResultSetIterable) EMPTY_RESULT_SET_ITERABLE;
}
public static ResultSetIterable getResultSetIterable(final ResultSet rs, final ResultSetToObject rsto) {
return getResultSetIterable(rs, rsto, null);
}
/**
* This is a convenience method meant to be called like this, where rs is a ResultSet
*
* ResultSetIterable rsi = ResultSetIterable.getResultSetIterable(rs, rs.next() ? complicatedBuildResultSetToObject(rs) : null, cal);
*
* This way you can avoid building or sending in a ResultSetToObject all together if there are no rows, therefore if rsto
* sent into this is null, it returns an EMPTY_RESULT_SET_ITERABLE and closes rs immediately
*
* This assumes rs.next() was called once before sent into this function
*/
@SuppressWarnings("unchecked")
public static ResultSetIterable getResultSetIterable(final ResultSet rs, final ResultSetToObject rsto, final Calendar cal) {
if (rsto == null) {
tryClose(rs); // have to do this here...
return (ResultSetIterable) EMPTY_RESULT_SET_ITERABLE;
}
final ResultSetIterable ret = new ResultSetIterable(rs, rsto, cal);
ret.calledNext = true;
return ret;
}
/**
* This is a convenience method meant to be called like this, where rs is a ResultSet
*
* Stream rsi = ResultSetIterable.getStream(rs, rs.next() ? complicatedBuildResultSetToObject(rs) : null, cal);
*
* This way you can avoid building or sending in a ResultSetToObject all together if there are no rows, therefore if rsto
* sent into this is null, it returns an empty Stream, rs might be closed before returning the empty Stream or on .close() to the Stream
*
* The stream returned MUST be closed in a try-with-resources or finally because the ResultSet is held open until then
*
* This assumes rs.next() was called once before sent into this function
*/
//IFJAVA8_START
@SuppressWarnings("unchecked")
public static Stream getStream(final ResultSet rs, final ResultSetToObject rsto, final Calendar cal) {
final Stream ret;
if (rsto == null) {
tryClose(rs); // have to do this here...
// todo: static object for this?
return (Stream) StreamSupport.stream(Spliterators.emptySpliterator(), false);
} else {
final ResultSetIterable rsi = new ResultSetIterable(rs, rsto, cal);
rsi.calledNext = true;
ret = StreamSupport.stream(rsi.spliterator(), false);
}
return ret.onClose(() -> tryClose(rs));
}
//IFJAVA8_END
private final ResultSet rs;
private final Calendar cal;
private final ResultSetToObject rsto;
private boolean calledNext = false;
private PreparedStatement ps = null;
protected ResultSetIterable() {
this.rs = null;
this.cal = null;
this.rsto = null;
}
public ResultSetIterable(final ResultSet rs, final ResultSetToObject rsto) {
this(rs, rsto, null);
}
public ResultSetIterable(final ResultSet rs, final ResultSetToObject rsto, final Calendar cal) {
if (rs == null || rsto == null)
throw new NullPointerException("rs and rsto must be non-null");
this.rs = rs;
this.cal = cal;
this.rsto = rsto;
}
public ResultSetIterable setPreparedStatementToClose(final PreparedStatement ps) {
if (this.ps != null)
throw new IllegalStateException("can only set PreparedStatement to close once");
this.ps = ps;
return this;
}
@Override
public Iterator iterator() {
return this;
}
@Override
public boolean hasNext() {
if (calledNext)
return true;
try {
calledNext = true;
return rs.next();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public T next() {
try {
if (!calledNext)
rs.next();
else
calledNext = false;
return rsto.toObject(rs, cal);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove unsupported");
}
@Override
public void close() {
tryClose(rs);
tryClose(ps);
}
private static class EmptyResultSetIterable extends ResultSetIterable {
@Override
public ResultSetIterable setPreparedStatementToClose(final PreparedStatement ps) {
tryClose(ps);
return this;
}
@Override
public boolean hasNext() {
return false;
}
@Override
public Object next() {
throw new NoSuchElementException("empty ResultSet");
}
@Override
public void close() {
// do nothing
}
}
}