From 2e6fce97866130c858a8b17d42aadbfbccd5db98 Mon Sep 17 00:00:00 2001
From: moparisthebest <admin@moparisthebest.com>
Date: Tue, 16 May 2017 16:56:53 -0400
Subject: [PATCH] Initial CompilingResultSetMapper implementation

---
 .../jdbc/CompilingResultSetMapper.java        |  27 ++
 .../jdbc/CompilingRowToObjectMapper.java      | 332 ++++++++++++++++++
 .../jdbc/util/ResultSetUtil.java              |  88 +++++
 3 files changed, 447 insertions(+)
 create mode 100644 beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/CompilingResultSetMapper.java
 create mode 100644 beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java
 create mode 100644 beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/util/ResultSetUtil.java

diff --git a/beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/CompilingResultSetMapper.java b/beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/CompilingResultSetMapper.java
new file mode 100644
index 0000000..dd66e5c
--- /dev/null
+++ b/beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/CompilingResultSetMapper.java
@@ -0,0 +1,27 @@
+package com.moparisthebest.jdbc;
+
+import com.moparisthebest.classgen.Compiler;
+
+import java.sql.ResultSet;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CompilingResultSetMapper extends ResultSetMapper {
+
+	protected final Compiler compiler = new Compiler();
+	protected final Map<CachingRowToObjectMapper.ResultSetKey, CompilingRowToObjectMapper.ResultSetToObject<?>> cache = new HashMap<CachingRowToObjectMapper.ResultSetKey, CompilingRowToObjectMapper.ResultSetToObject<?>>();
+
+	public CompilingResultSetMapper(Calendar cal, int arrayMaxLength) {
+		super(cal, arrayMaxLength);
+	}
+
+	public CompilingResultSetMapper() {
+		super();
+	}
+
+	@Override
+	protected <T> RowToObjectMapper<T> getRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType) {
+		return new CompilingRowToObjectMapper<T>(compiler, cache, resultSet, returnTypeClass, cal, mapValType);
+	}
+}
diff --git a/beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java b/beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java
new file mode 100644
index 0000000..a79246c
--- /dev/null
+++ b/beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/CompilingRowToObjectMapper.java
@@ -0,0 +1,332 @@
+package com.moparisthebest.jdbc;
+
+import com.moparisthebest.classgen.Compiler;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Calendar;
+import java.util.Map;
+
+/**
+ * Created by mopar on 5/16/17.
+ */
+public class CompilingRowToObjectMapper<T> extends RowToObjectMapper<T> {
+	protected final Compiler compiler;
+	protected final ResultSetToObject<T> resultSetToObject;
+
+	protected String[] keys = null; // for caching if we must generate class
+
+	public CompilingRowToObjectMapper(final Compiler compiler, final Map<CachingRowToObjectMapper.ResultSetKey, ResultSetToObject<?>> cache, ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType) {
+		super(resultSet, returnTypeClass, cal, mapValType);
+		this.compiler = compiler;
+		try {
+			final CachingRowToObjectMapper.ResultSetKey keys = new CachingRowToObjectMapper.ResultSetKey(super.getKeysFromResultSet(), _returnTypeClass);
+			//System.out.printf("keys: %s\n", keys);
+			@SuppressWarnings("unchecked")
+			final ResultSetToObject<T> resultSetToObject = (ResultSetToObject<T>) cache.get(keys);
+			if (resultSetToObject == null) {
+				//System.out.printf("cache miss, keys: %s\n", keys);
+				// generate and put into cache
+				this.keys = keys.keys;
+				cache.put(keys, this.resultSetToObject = genClass());
+				this.keys = null;
+				this._fields = null;
+				this._fieldTypes = null;
+			} else {
+				//System.out.printf("cache hit, keys: %s\n", keys);
+				// load from cache
+				this.resultSetToObject = resultSetToObject;
+			}
+		} catch (SQLException e) {
+			throw new MapperException("CachingRowToObjectMapper: SQLException: " + e.getMessage(), e);
+		}
+	}
+
+	@Override
+	public T mapRowToReturnType() {
+		try {
+			return resultSetToObject.toObject(_resultSet, _cal);
+		} catch (SQLException e) {
+			throw new MapperException(e.getMessage(), e);
+		}
+	}
+
+	@Override
+	protected String[] getKeysFromResultSet() throws SQLException {
+		if (keys == null)
+			throw new MapperException("not supported here");
+		return keys;
+	}
+
+	@Override
+	protected void getFieldMappings() throws SQLException {
+		throw new MapperException("not supported here");
+	}
+
+	public interface ResultSetToObject<T> {
+		T toObject(final ResultSet rs, final Calendar cal) throws SQLException;
+	}
+
+	protected String typeFromName(final Class<?> type) {
+		return type.getName(); // todo: naive for now
+	}
+
+	protected String escapeJavaString(final String s) {
+		// todo: escape key string, newlines, double quotes, backslashes...
+		return s;
+	}
+
+	// code generation down here
+	protected ResultSetToObject<T> genClass() {
+		final String className = "CompilingMapper";
+		final String tType = typeFromName(_returnTypeClass);
+		final String header =
+				"import static com.moparisthebest.jdbc.util.ResultSetUtil.*;\n\n" +
+						"public final class " + className +
+						" implements com.moparisthebest.jdbc.CompilingRowToObjectMapper.ResultSetToObject<" + tType + "> {\n" +
+						"  public " + tType + " toObject(final java.sql.ResultSet rs, final java.util.Calendar cal) throws java.sql.SQLException {\n";
+		final String footer = "  }\n" +
+				"}\n";
+
+		final StringBuilder java = new StringBuilder(header);
+		//java.append("return null;\n");
+		gen(java, tType);
+		java.append(footer);
+		//System.out.println(java);
+		return compiler.compile(className, java);
+	}
+
+	protected void gen(final StringBuilder java, final String tType) {
+
+		if (resultSetConstructor) {
+			java.append("return new ").append(tType).append("(rs);\n");
+			return;
+		}
+
+		if (returnMap) // we want a map
+			try {
+				// todo: does not call getMapImplementation, I think that's fine
+				java.append("final Map<String, Object> ret = new ").append(tType).append("<String, Object>();\n");
+				final ResultSetMetaData md = _resultSet.getMetaData();
+				final int columnLength = _columnCount + 1;
+				if (componentType != null && componentType != Object.class) { // we want a specific value type
+					int typeId = _tmf.getTypeId(componentType);
+					for (int x = 1; x < columnLength; ++x) {
+						java.append("ret.put(").append(escapeJavaString(md.getColumnName(x).toLowerCase())).append(", ");
+						extractColumnValueString(java, x, typeId);
+						java.append(");\n");
+					}
+				} else // we want a generic object type
+					for (int x = 1; x < columnLength; ++x)
+						java.append("ret.put(").append(escapeJavaString(md.getColumnName(x).toLowerCase())).append(", rs.getObject(").append(x).append("));\n");
+				java.append("return ret;\n");
+				return;
+			} catch (Throwable e) {
+				throw new MapperException(e.getClass().getName() + " when trying to create a Map<String, "
+						+ (componentType == null ? "java.lang.Object" : componentType.getName()) + "> from a ResultSet row" +
+						", all columns must be of the map value type", e);
+			}
+		else if (componentType != null) // we want an array
+			try {
+				// todo: array initialization syntax?
+				java.append("final ").append(tType).append("[] ret = new ").append(tType).append("[").append(_columnCount).append("];\n");
+				final int typeId = _tmf.getTypeId(componentType);
+				for (int x = 0; x < _columnCount; ) {
+					java.append("ret[").append(x).append("] = ");
+					extractColumnValueString(java, ++x, typeId);
+					java.append(";\n");
+				}
+				java.append("return ret;\n");
+			} catch (Throwable e) {
+				throw new MapperException(e.getClass().getName() + " when trying to create a "
+						+ componentType.getName() + "[] from a ResultSet row, all columns must be of that type", e);
+			}
+
+		// if the ResultSet only contains a single column we may be able to map directly
+		// to the return type -- if so we don't need to build any structures to support
+		// mapping
+		if (_columnCount == 1) {
+
+			final int typeId = _tmf.getTypeId(_returnTypeClass);
+
+			try {
+				if (typeId != TypeMappingsFactory.TYPE_UNKNOWN) {
+					java.append("return ");
+					extractColumnValueString(java, 1, typeId);
+					java.append(";\n");
+					return;
+				} else {
+					// we still might want a single value (i.e. java.util.Date)
+					/*
+					Object val = extractColumnValue(1, typeId);
+					if (_returnTypeClass.isAssignableFrom(val.getClass())) {
+						return _returnTypeClass.cast(val);
+					}
+					*/
+					// we could actually pull from first row like above and test it first and fail now, but maybe just failing during compilation is enough?
+					java.append("return (").append(tType).append(") ");
+					extractColumnValueString(java, 1, typeId);
+					java.append(";\n");
+					return;
+				}
+			} catch (Exception e) {
+				throw new MapperException(e.getMessage(), e);
+			}
+		}
+
+		if (_fields == null) {
+			try {
+				super.getFieldMappings();
+			} catch (SQLException e) {
+				throw new MapperException(e.getMessage(), e);
+			}
+		}
+
+		java.append("final ").append(tType).append(" ret = new ").append(tType).append("();\n");
+
+		for (int i = 1; i < _fields.length; i++) {
+			AccessibleObject f = _fields[i];
+
+			//_args[0] = extractColumnValue(i, _fieldTypes[i]);
+			//System.out.printf("field: '%s' obj: '%s' fieldType: '%s'\n", _fields[i], _args[0], _fieldTypes[i]);
+			// custom hacked-in support for enums, can do better when we scrap org.apache.beehive.controls.system.jdbc.TypeMappingsFactory
+			if (_fieldTypes[i] == 0) {
+				final Class<?> fieldType = f instanceof Field ? ((Field) f).getType() : ((Method) f).getParameterTypes()[0];
+				if (Enum.class.isAssignableFrom(fieldType)) {
+					_args[0] = Enum.valueOf((Class<? extends Enum>) fieldType, (String) _args[0]);
+					if (f instanceof Field) {
+						// if f not accessible (but super.getFieldMappings() sets it), throw exception during compilation is fine
+						java.append("ret.").append(((Field) f).getName()).append(" = ").append(typeFromName(fieldType)).append(".valueOf(");
+						extractColumnValueString(java, i, _fieldTypes[i]);
+						java.append(");\n");
+					} else {
+						java.append("ret.").append(((Method) f).getName()).append("(").append(typeFromName(fieldType)).append(".valueOf(");
+						extractColumnValueString(java, i, _fieldTypes[i]);
+						java.append("));\n");
+					}
+				}
+			}
+			if (f instanceof Field) {
+				// if f not accessible (but super.getFieldMappings() sets it), throw exception during compilation is fine
+				java.append("ret.").append(((Field) f).getName()).append(" = ");
+				extractColumnValueString(java, i, _fieldTypes[i]);
+				java.append(";\n");
+			} else {
+				java.append("ret.").append(((Method) f).getName()).append("(");
+				extractColumnValueString(java, i, _fieldTypes[i]);
+				java.append(");\n");
+			}
+		}
+		// if this resultObject is Finishable, call finish()
+		if (Finishable.class.isAssignableFrom(_returnTypeClass))
+			java.append("ret.finish(rs);\n");
+		java.append("return ret;\n");
+	}
+
+	/**
+	 * Extract a column value from the ResultSet and return it as resultType.
+	 *
+	 * @param index      The column index of the value to extract from the ResultSet.
+	 * @param resultType The return type. Defined in TypeMappingsFactory.
+	 * @return The extracted value
+	 * @throws java.sql.SQLException on error.
+	 */
+	protected void extractColumnValueString(final StringBuilder java, final int index, final int resultType) {
+		// todo: custom boolean
+		switch (resultType) {
+			case TypeMappingsFactory.TYPE_INT:
+				java.append("rs.getInt(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_LONG:
+				java.append("rs.getLong(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_FLOAT:
+				java.append("rs.getFloat(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_DOUBLE:
+				java.append("rs.getDouble(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_BYTE:
+				java.append("rs.getByte(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_SHORT:
+				java.append("rs.getInt(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_BOOLEAN:
+				java.append("rs.getInt(").append(index).append(") ? Boolean.TRUE : Boolean.FALSE");
+				return;
+			case TypeMappingsFactory.TYPE_INT_OBJ:
+				java.append("getObjectInt(rs, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_LONG_OBJ:
+				java.append("getObjectLong(rs, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_FLOAT_OBJ:
+				java.append("getObjectFloat(rs, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_DOUBLE_OBJ:
+				java.append("getObjectDouble(rs, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_BYTE_OBJ:
+				java.append("getObjectByte(rs, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_SHORT_OBJ:
+				java.append("getObjectShort(rs, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_BOOLEAN_OBJ:
+				java.append("getObjectBoolean(rs, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_STRING:
+			case TypeMappingsFactory.TYPE_XMLBEAN_ENUM:
+				java.append("rs.getString(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_BIG_DECIMAL:
+				java.append("rs.getBigDecimal(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_BYTES:
+				java.append("rs.getBytes(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_TIMESTAMP:
+				java.append("getTimestamp(rs, cal, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_TIME:
+				java.append("getTime(rs, cal, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_SQLDATE:
+				java.append("getSqlDate(rs, cal, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_DATE:
+				java.append("getUtilDate(rs, cal, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_CALENDAR:
+				java.append("getCalendar(rs, cal, ").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_REF:
+				java.append("rs.getRef(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_BLOB:
+				java.append("rs.getBlob(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_CLOB:
+				java.append("rs.getClob(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_ARRAY:
+				java.append("rs.getArray(").append(index).append(")");
+				return;
+			case TypeMappingsFactory.TYPE_READER:
+			case TypeMappingsFactory.TYPE_STREAM:
+				throw new MapperException("streaming return types are not supported by the JdbcControl; use ResultSet instead");
+			case TypeMappingsFactory.TYPE_STRUCT:
+			case TypeMappingsFactory.TYPE_UNKNOWN:
+				// JAVA_TYPE (could be any), or REF
+				java.append("rs.getObject(").append(index).append(")");
+				return;
+			default:
+				throw new MapperException("internal error: unknown type ID: " + Integer.toString(resultType));
+		}
+	}
+}
diff --git a/beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/util/ResultSetUtil.java b/beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/util/ResultSetUtil.java
new file mode 100644
index 0000000..0a4b3e4
--- /dev/null
+++ b/beehive-jdbc-mapper/src/main/java/com/moparisthebest/jdbc/util/ResultSetUtil.java
@@ -0,0 +1,88 @@
+package com.moparisthebest.jdbc.util;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+
+/**
+ * Created by mopar on 5/16/17.
+ */
+public class ResultSetUtil {
+
+	public static Integer getObjectInt(final ResultSet rs, final int index) throws SQLException {
+		final int ret = rs.getInt(index);
+		return rs.wasNull() ? null : ret;
+	}
+
+	public static Long getObjectLong(final ResultSet rs, final int index) throws SQLException {
+		final long ret = rs.getLong(index);
+		return rs.wasNull() ? null : ret;
+	}
+
+	public static Float getObjectFloat(final ResultSet rs, final int index) throws SQLException {
+		final float ret = rs.getFloat(index);
+		return rs.wasNull() ? null : ret;
+	}
+
+	public static Double getObjectDouble(final ResultSet rs, final int index) throws SQLException {
+		final double ret = rs.getDouble(index);
+		return rs.wasNull() ? null : ret;
+	}
+
+	public static Byte getObjectByte(final ResultSet rs, final int index) throws SQLException {
+		final byte ret = rs.getByte(index);
+		return rs.wasNull() ? null : ret;
+	}
+
+	public static Short getObjectShort(final ResultSet rs, final int index) throws SQLException {
+		final short ret = rs.getShort(index);
+		return rs.wasNull() ? null : ret;
+	}
+
+	public static Boolean getObjectBoolean(final ResultSet rs, final int index) throws SQLException {
+		final boolean ret = rs.getBoolean(index);
+		return rs.wasNull() ? null : (ret ? Boolean.TRUE : Boolean.FALSE);
+	}
+
+	public static Timestamp getTimestamp(final ResultSet _resultSet, final Calendar _cal, final int index) throws SQLException {
+		if (null == _cal)
+			return _resultSet.getTimestamp(index);
+		else
+			return _resultSet.getTimestamp(index, _cal);
+	}
+
+	public static Time getTime(final ResultSet _resultSet, final Calendar _cal, final int index) throws SQLException {
+		if (null == _cal)
+			return _resultSet.getTime(index);
+		else
+			return _resultSet.getTime(index, _cal);
+	}
+
+	public static java.sql.Date getSqlDate(final ResultSet _resultSet, final Calendar _cal, final int index) throws SQLException {
+		if (null == _cal)
+			return _resultSet.getDate(index);
+		else
+			return _resultSet.getDate(index, _cal);
+	}
+
+	public static java.util.Date getUtilDate(final ResultSet _resultSet, final Calendar _cal, final int index) throws SQLException {
+		// convert explicity to java.util.Date
+		// 12918 |  knex does not return java.sql.Date properly from web service
+		java.sql.Timestamp ts = (null == _cal) ? _resultSet.getTimestamp(index) : _resultSet.getTimestamp(index, _cal);
+		if (null == ts)
+			return null;
+		return new java.util.Date(ts.getTime());
+	}
+
+	public static Calendar getCalendar(final ResultSet _resultSet, final Calendar _cal, final int index) throws SQLException {
+		java.sql.Timestamp ts = (null == _cal) ? _resultSet.getTimestamp(index) : _resultSet.getTimestamp(index, _cal);
+		if (null == ts)
+			return null;
+		Calendar c = (null == _cal) ? Calendar.getInstance() : (Calendar) _cal.clone();
+		c.setTimeInMillis(ts.getTime());
+		return c;
+	}
+
+}