package net.ttddyy.dsproxy.proxy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.lang.Boolean.TRUE;
import static java.lang.String.format;
/**
* Allows {@link java.sql.ResultSet} to be consumed more than once.
*
* @author Liam Williams
* @see net.ttddyy.dsproxy.proxy.jdk.ResultSetInvocationHandler
* @see net.ttddyy.dsproxy.proxy.jdk.ResultSetProxyJdbcProxyFactory
* @see net.ttddyy.dsproxy.proxy.jdk.StatementResultSetResultInvocationHandler
* @since 1.4
*/
public class ResultSetProxyLogic {
private final Map<String, Integer> columnNameToIndex;
private final ResultSet target;
private final int columnCount;
private int resultPointer;
private boolean resultSetConsumed;
private boolean closed;
private Object[] currentResult;
private final List<Object[]> cachedResults = new ArrayList<Object[]>();
private ResultSetProxyLogic(Map<String, Integer> columnNameToIndex, ResultSet target, int columnCount) throws SQLException {
this.columnNameToIndex = columnNameToIndex;
this.target = target;
this.columnCount = columnCount;
}
public static ResultSetProxyLogic resultSetProxyLogic(ResultSet target) throws SQLException {
ResultSetMetaData metaData = target.getMetaData();
int columnCount = metaData.getColumnCount();
return new ResultSetProxyLogic(columnNameToIndex(metaData), target, columnCount);
}
private static Map<String, Integer> columnNameToIndex(ResultSetMetaData metaData) throws SQLException {
Map<String, Integer> columnNameToIndex = new HashMap<String, Integer>();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
columnNameToIndex.put(metaData.getColumnLabel(i).toUpperCase(), i);
}
return columnNameToIndex;
}
public Object invoke(Method method, Object[] args) throws Throwable {
if (isGetTargetMethod(method)) {
// ProxyJdbcObject interface has a method to return original object.
return target;
}
if (isGetMetaDataMethod(method)) {
return method.invoke(target, args);
}
if (isCloseMethod(method)) {
closed = true;
return method.invoke(target, args);
}
if (closed) {
throw new SQLException("Already closed");
}
if (resultSetConsumed) {
if (isGetMethod(method)) {
return handleGetMethodUsingCache(args);
}
if (isNextMethod(method)) {
return handleNextMethodUsingCache();
}
} else {
if (isGetMethod(method)) {
return handleGetMethodByDelegating(method, args);
}
if (isNextMethod(method)) {
return handleNextMethodByDelegating(method, args);
}
if (isBeforeFirstMethod(method)) {
resultPointer = -1;
resultSetConsumed = true;
return null;
}
}
throw new UnsupportedOperationException(format("Method '%s' is not supported by this proxy", method));
}
private Object handleNextMethodByDelegating(Method method, Object[] args) throws IllegalAccessException, InvocationTargetException {
Object result = method.invoke(target, args);
if (TRUE.equals(result)) {
currentResult = new Object[columnCount + 1];
cachedResults.add(currentResult);
}
return result;
}
private Object handleGetMethodByDelegating(Method method, Object[] args) throws SQLException, IllegalAccessException, InvocationTargetException {
int columnIndex = determineColumnIndex(args);
Object result = method.invoke(target, args);
currentResult[columnIndex] = result;
return result;
}
private Object handleNextMethodUsingCache() {
if (resultPointer < cachedResults.size() - 1) {
resultPointer++;
currentResult = cachedResults.get(resultPointer);
return true;
} else {
resultPointer++;
currentResult = null;
return false;
}
}
private Object handleGetMethodUsingCache(Object[] args) throws SQLException {
if (resultPointer == -1) {
throw new SQLException("Result set not advanced. Call next before any get method!");
} else if (resultPointer < cachedResults.size()) {
int columnIndex = determineColumnIndex(args);
return currentResult[columnIndex];
} else {
throw new SQLException(format("Result set exhausted. There were %d result(s) only", cachedResults.size()));
}
}
private boolean isCloseMethod(Method method) {
return method.getName().equals("close");
}
private boolean isGetTargetMethod(Method method) {
return method.getName().equals("getTarget");
}
private boolean isGetMetaDataMethod(Method method) {
return method.getName().equals("getMetaData");
}
private boolean isGetMethod(Method method) {
return method.getName().startsWith("get") && method.getParameterTypes().length > 0;
}
private boolean isNextMethod(Method method) {
return method.getName().equals("next");
}
private boolean isBeforeFirstMethod(Method method) {
return method.getName().equals("beforeFirst");
}
private int determineColumnIndex(Object[] args) throws SQLException {
Object lookup = args[0];
if (lookup instanceof Integer) {
return (Integer) lookup;
}
String columnName = (String) lookup;
Integer indexForColumnName = columnNameToIndex(columnName);
if (indexForColumnName != null) {
return indexForColumnName;
} else {
throw new SQLException(format("Unknown column name '%s'", columnName));
}
}
private Integer columnNameToIndex(String columnName) {
return columnNameToIndex.get(columnName.toUpperCase());
}
}