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 } } }