/*
 * Copyright 2002-2017 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.jdbc.core;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;

import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.lang.Nullable;

/**
 * Implementation of RowCallbackHandler. Convenient superclass for callback handlers.
 * An instance can only be used once.
 *
 * <p>We can either use this on its own (for example, in a test case, to ensure
 * that our result sets have valid dimensions), or use it as a superclass
 * for callback handlers that actually do something, and will benefit
 * from the dimension information it provides.
 *
 * <p>A usage example with JdbcTemplate:
 *
 * <pre class="code">JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);  // reusable object
 *
 * RowCountCallbackHandler countCallback = new RowCountCallbackHandler();  // not reusable
 * jdbcTemplate.query("select * from user", countCallback);
 * int rowCount = countCallback.getRowCount();</pre>
 *
 * @author Rod Johnson
 * @since May 3, 2001
 */
public class RowCountCallbackHandler implements RowCallbackHandler {

	/** Rows we've seen so far */
	private int rowCount;

	/** Columns we've seen so far */
	private int columnCount;

	/**
	 * Indexed from 0. Type (as in java.sql.Types) for the columns
	 * as returned by ResultSetMetaData object.
	 */
	@Nullable
	private int[] columnTypes;

	/**
	 * Indexed from 0. Column name as returned by ResultSetMetaData object.
	 */
	@Nullable
	private String[] columnNames;


	/**
	 * Implementation of ResultSetCallbackHandler.
	 * Work out column size if this is the first row, otherwise just count rows.
	 * <p>Subclasses can perform custom extraction or processing
	 * by overriding the {@code processRow(ResultSet, int)} method.
	 * @see #processRow(java.sql.ResultSet, int)
	 */
	@Override
	public final void processRow(ResultSet rs) throws SQLException {
		if (this.rowCount == 0) {
			ResultSetMetaData rsmd = rs.getMetaData();
			this.columnCount = rsmd.getColumnCount();
			this.columnTypes = new int[this.columnCount];
			this.columnNames = new String[this.columnCount];
			for (int i = 0; i < this.columnCount; i++) {
				this.columnTypes[i] = rsmd.getColumnType(i + 1);
				this.columnNames[i] = JdbcUtils.lookupColumnName(rsmd, i + 1);
			}
			// could also get column names
		}
		processRow(rs, this.rowCount++);
	}

	/**
	 * Subclasses may override this to perform custom extraction
	 * or processing. This class's implementation does nothing.
	 * @param rs ResultSet to extract data from. This method is
	 * invoked for each row
	 * @param rowNum number of the current row (starting from 0)
	 */
	protected void processRow(ResultSet rs, int rowNum) throws SQLException {
	}


	/**
	 * Return the types of the columns as java.sql.Types constants
	 * Valid after processRow is invoked the first time.
	 * @return the types of the columns as java.sql.Types constants.
	 * <b>Indexed from 0 to n-1.</b>
	 */
	@Nullable
	public final int[] getColumnTypes() {
		return this.columnTypes;
	}

	/**
	 * Return the names of the columns.
	 * Valid after processRow is invoked the first time.
	 * @return the names of the columns.
	 * <b>Indexed from 0 to n-1.</b>
	 */
	@Nullable
	public final String[] getColumnNames() {
		return this.columnNames;
	}

	/**
	 * Return the row count of this ResultSet
	 * Only valid after processing is complete
	 * @return the number of rows in this ResultSet
	 */
	public final int getRowCount() {
		return this.rowCount;
	}

	/**
	 * Return the number of columns in this result set.
	 * Valid once we've seen the first row,
	 * so subclasses can use it during processing
	 * @return the number of columns in this result set
	 */
	public final int getColumnCount() {
		return this.columnCount;
	}

}
