// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.pgsimple.common; import java.sql.ResultSet; import java.sql.SQLException; import java.util.NoSuchElementException; import java.util.logging.Level; import java.util.logging.Logger; import org.openstreetmap.osmosis.core.OsmosisRuntimeException; import org.openstreetmap.osmosis.core.lifecycle.ReleasableIterator; /** * Provides the base implementation of all database table readers. * * @author Brett Henderson * * @param <T> * The type of entity to retrieved. */ public abstract class BaseTableReader<T> implements ReleasableIterator<T> { private static final Logger LOG = Logger.getLogger(BaseTableReader.class.getName()); private DatabaseContext dbCtx; private ResultSet resultSet; private T nextValue; /** * Creates a new instance. * * @param dbCtx * The active connection to use for reading from the database. */ public BaseTableReader(DatabaseContext dbCtx) { this.dbCtx = dbCtx; } /** * Builds the result set that the reader will iterate over. * * @param queryDbCtx * The database context to query against. * @return A result set positioned before the first record. */ protected abstract ResultSet createResultSet(DatabaseContext queryDbCtx); /** * Builds an entity object from the current recordset row. * * @param activeResultSet * The record set to retrieve the data from. * @return The result of the read. */ protected abstract ReadResult<T> createNextValue(ResultSet activeResultSet); /** * If the implementation requires multiple rows to build an entity object, * this method allows the implementation to return an entity based on the * fact that no more rows are available. This default implementation returns * a blank result. * * @return The last result record. */ protected ReadResult<T> createLastValue() { return new ReadResult<T>(true, null); } /** * Reads the next entity from the database and stores it in the internal * nextValue variable. This will be set to null if no more data is * available. */ private void readNextValue() { if (resultSet == null) { resultSet = createResultSet(dbCtx); } try { ReadResult<T> readResult; // Loop until a valid result is determined. Typically a loop is // required when a record on the result set is skipped over by the // reader implementation. do { if (resultSet.next()) { readResult = createNextValue(resultSet); } else { readResult = createLastValue(); } } while (!readResult.isUsableResult()); nextValue = readResult.getEntity(); } catch (SQLException e) { throw new OsmosisRuntimeException("Unable to move to next record.", e); } } /** * {@inheritDoc} */ public boolean hasNext() { if (resultSet == null) { readNextValue(); } return (nextValue != null); } /** * {@inheritDoc} */ public T next() { T result; if (!hasNext()) { throw new NoSuchElementException(); } result = nextValue; readNextValue(); return result; } /** * {@inheritDoc} */ public void close() { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { // We cannot throw an exception within a release method. LOG.log(Level.WARNING, "Unable to close result set.", e); } resultSet = null; } } /** * {@inheritDoc} */ public void remove() { throw new UnsupportedOperationException(); } /** * Represents the result of an entity read from the result set at the current position. * * @param <T> * The type of entity to retrieved. */ protected static class ReadResult<T> { private boolean usableResult; private T entity; /** * Creates a new instance. * * @param usableResult * Indicates if this result should be used. * @param entity * The entity being read. */ public ReadResult(boolean usableResult, T entity) { this.usableResult = usableResult; this.entity = entity; } /** * Returns the usable result flag. * * @return The usable result flag. */ public boolean isUsableResult() { return usableResult; } /** * Returns the entity. * * @return The entity. */ public T getEntity() { return entity; } } }