/*
 * Copyright 2017, TeamDev Ltd. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.server.storage.jdbc;

import io.spine.annotation.Internal;

import java.io.Closeable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * An iterator over a {@link ResultSet} of storage records.
 *
 * <p>This class internally uses {@link ResultSet}.
 * See how to {@linkplain #close() finish} the usage of the iterator properly.
 *
 * <p>Uses {@link Serializer} to deserialize records.
 *
 * <p><b>NOTE:</b> {@code remove} operation is not supported.
 *
 * @param <R> type of storage records
 * @author Alexander Litus
 */
@Internal
public abstract class DbIterator<R> implements Iterator<R>, Closeable {

    private final ResultSet resultSet;
    private final String columnName;
    private boolean hasNextCalled = false;
    private boolean nextCalled = true;
    private boolean memoizedHasNext = false;

    /**
     * Creates a new iterator instance.
     *
     * @param statement  a statement used to retrieve a result set
     *                   (both statement and result set are closed in {@link #close()}).
     * @param columnName a name of a serialized storage record column
     * @throws DatabaseException if an error occurs during interaction with the DB
     */
    protected DbIterator(PreparedStatement statement, String columnName) throws DatabaseException {
        try {
            this.resultSet = statement.executeQuery();
            this.columnName = columnName;
        } catch (SQLException e) {
            throw new DatabaseException(e);
        }
    }

    /**
     * Creates a new iterator instance.
     *
     * @param resultSet  the results of a DB query to iterate over
     * @param columnName a name of a serialized storage record column
     * @throws DatabaseException if an error occurs during interaction with the DB
     */
    protected DbIterator(ResultSet resultSet, String columnName) {
        this.resultSet = resultSet;
        this.columnName = columnName;
    }

    /**
     * {@inheritDoc}
     *
     * <p>Calls {@link #close()}, if {@link ResultSet#next()} returns {@code false}.
     *
     * @return {@inheritDoc}
     */
    @Override
    public boolean hasNext() {
        if (!nextCalled) {
            return memoizedHasNext;
        }
        try {
            final boolean hasNextElem = resultSet.next();
            if (!hasNextElem) {
                close();
            }

            // ResultSet.previous() is not used here because some JDBC drivers do not support it.
            memoizedHasNext = hasNextElem;

            hasNextCalled = true;
            nextCalled = false;

            return hasNextElem;
        } catch (SQLException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public R next() {
        if (!memoizedHasNext && (hasNextCalled || !hasNext())) {
            throw noSuchElement();
        }

        hasNextCalled = false;
        memoizedHasNext = false;
        nextCalled = true;

        final R result;
        try {
            result = readResult();
        } catch (SQLException e) {
            throw new DatabaseException(e);
        }
        return result;
    }

    protected abstract R readResult() throws SQLException;

    protected ResultSet getResultSet() {
        return resultSet;
    }

    protected String getColumnName() {
        return columnName;
    }

    /**
     * Removal is unsupported.
     *
     * @throws UnsupportedOperationException always
     * @deprecated as unsupported
     */
    @Override
    @Deprecated
    public final void remove() throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Removing is not supported.");
    }

    /**
     * Closes {@link #resultSet} and the related {@link Statement} and {@link Connection}.
     *
     * <p>This method should be called either manually or called by {@link #hasNext()}.
     *
     * @throws DatabaseException if {@code SQLException} is occurred
     */
    @Override
    public void close() throws DatabaseException {
        try {
            resultSet.close();
            final Statement statement = resultSet.getStatement();
            if (statement != null) {
                statement.close();
                final Connection connection = statement.getConnection();
                if (connection != null) {
                    connection.close();
                }
            }
        } catch (SQLException e) {
            throw new DatabaseException(e);
        }
    }

    private static NoSuchElementException noSuchElement() {
        throw new NoSuchElementException("No elements remained.");
    }
}

@@ -5316,29 +5316,115 @@
     
-if (statement != null
+final boolean statementClosed = statement == null %7C%7C statement.isClosed();%0A            if (!statementClosed
 ) %7B%0A
@@ -5551,30 +5551,123 @@
     
-if (connection != null
+final boolean connectionClosed = connection == null %7C%7C connection.isClosed();%0A                if (!connectionClosed
 ) %7B%0A

Improve closing of `DbIterator`