Initial JdbcMapper annotation support
This commit is contained in:
parent
67ae0d257c
commit
d8f7e6df4a
@ -22,9 +22,37 @@
|
||||
<groupId>org.apache.derby</groupId>
|
||||
<artifactId>derby</artifactId>
|
||||
<scope>test</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<debug>true</debug>
|
||||
<!--compilerArgument>-Xlint:unchecked</compilerArgument-->
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>my-testCompile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<annotationProcessors>
|
||||
<annotationProcessor>com.moparisthebest.jdbc.codegen.JdbcMapperProcessor</annotationProcessor>
|
||||
</annotationProcessors>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
@ -0,0 +1,26 @@
|
||||
package com.moparisthebest.classgen;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/25/17.
|
||||
*/
|
||||
public abstract class AbstractSQLParser implements SQLParser {
|
||||
|
||||
private final String[] columnNames;
|
||||
private final boolean isSelect;
|
||||
|
||||
protected AbstractSQLParser(final String[] columnNames, final boolean isSelect) {
|
||||
this.columnNames = columnNames;
|
||||
this.isSelect = isSelect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String[] columnNames() {
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isSelect() {
|
||||
return isSelect;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.moparisthebest.classgen;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/25/17.
|
||||
*/
|
||||
public interface SQLParser {
|
||||
|
||||
/**
|
||||
* Return SQLParser instance for given SQL
|
||||
*
|
||||
* @param sql SQL to parse
|
||||
* @return instance with string parsed
|
||||
*/
|
||||
SQLParser parse(String sql);
|
||||
|
||||
/**
|
||||
* @return column names for select, with 1-based index like ResultSet, index 0 is always null, not used if isSelect() returns false
|
||||
*/
|
||||
String[] columnNames();
|
||||
|
||||
/**
|
||||
* Return
|
||||
*
|
||||
* @return true for Select, if we will map to an object, false to call executeUpdate (insert/update/merge/?)
|
||||
*/
|
||||
boolean isSelect();
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.moparisthebest.classgen;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/25/17.
|
||||
*/
|
||||
public class SimpleSQLParser extends AbstractSQLParser {
|
||||
|
||||
private static final Pattern aliasPattern = Pattern.compile("^.*\\.");
|
||||
|
||||
public SimpleSQLParser() {
|
||||
super(null, false);
|
||||
}
|
||||
|
||||
|
||||
private SimpleSQLParser(final String[] columnNames, final boolean isSelect) {
|
||||
super(columnNames, isSelect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLParser parse(String sql) {
|
||||
sql = sql.toUpperCase();
|
||||
final boolean isSelect = sql.startsWith("SELECT");
|
||||
String[] columnNames = null;
|
||||
if (isSelect) {
|
||||
final String columns = sql.substring(sql.indexOf("SELECT") + 6, sql.indexOf("FROM")).trim();
|
||||
final String[] splitColumns = columns.split(",");
|
||||
columnNames = new String[splitColumns.length + 1];
|
||||
int index = 0;
|
||||
for (String column : splitColumns) {
|
||||
final String[] singleSplit = column.split("\\s");
|
||||
// trim and remove leading table/alias
|
||||
columnNames[++index] = aliasPattern.matcher(singleSplit[singleSplit.length - 1].trim()).replaceFirst("");
|
||||
}
|
||||
}
|
||||
return new SimpleSQLParser(columnNames, isSelect);
|
||||
}
|
||||
}
|
@ -58,34 +58,36 @@ public abstract class AbstractRowMapper<K, T> implements RowMapper<K,T> {
|
||||
* @param returnTypeClass Class to map ResultSet rows to.
|
||||
* @param cal Calendar instance for date/time values.
|
||||
*/
|
||||
protected AbstractRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<K> mapKeyType) {
|
||||
protected AbstractRowMapper(String[] keys, ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<K> mapKeyType) {
|
||||
_resultSet = resultSet;
|
||||
_returnTypeClass = returnTypeClass;
|
||||
_cal = cal;
|
||||
_mapKeyType = mapKeyType;
|
||||
|
||||
try {
|
||||
_columnCount = resultSet.getMetaData().getColumnCount();
|
||||
} catch (SQLException e) {
|
||||
throw new MapperException("RowToObjectMapper: SQLException: " + e.getMessage(), e);
|
||||
if(keys == null) {
|
||||
try {
|
||||
_columnCount = resultSet.getMetaData().getColumnCount();
|
||||
} catch (SQLException e) {
|
||||
throw new MapperException("RowToObjectMapper: SQLException: " + e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
this.keys = keys;
|
||||
_columnCount = keys.length - 1;
|
||||
}
|
||||
|
||||
mapOnlySecondColumn = _mapKeyType != null && _columnCount == 2;
|
||||
}
|
||||
|
||||
protected AbstractRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<K> mapKeyType) {
|
||||
this(null, resultSet, returnTypeClass, cal, mapKeyType);
|
||||
}
|
||||
|
||||
protected AbstractRowMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal) {
|
||||
this(resultSet, returnTypeClass, cal, null);
|
||||
}
|
||||
|
||||
protected AbstractRowMapper(String[] keys, Class<T> returnTypeClass, Calendar cal, Class<K> mapKeyType) {
|
||||
_resultSet = null;
|
||||
_returnTypeClass = returnTypeClass;
|
||||
_cal = cal;
|
||||
_mapKeyType = mapKeyType;
|
||||
this.keys = keys;
|
||||
_columnCount = keys.length - 1;
|
||||
|
||||
mapOnlySecondColumn = _mapKeyType != null && _columnCount == 2;
|
||||
this(keys, null, returnTypeClass, cal, mapKeyType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,11 +2,11 @@ package com.moparisthebest.jdbc;
|
||||
|
||||
import com.moparisthebest.classgen.Compiler;
|
||||
|
||||
import java.io.IOException;
|
||||
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;
|
||||
@ -57,9 +57,17 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new MapperException("CachingRowToObjectMapper: SQLException: " + e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
throw new MapperException("CachingRowToObjectMapper: IOException (should never happen?): " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public CompilingRowToObjectMapper(final String[] keys, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
|
||||
super(keys,null, returnTypeClass, cal, mapValType, mapKeyType, false);
|
||||
this.compiler = null;
|
||||
this.resultSetToObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T mapRowToReturnType() throws SQLException {
|
||||
return resultSetToObject.toObject(_resultSet, _cal);
|
||||
@ -138,7 +146,7 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
}
|
||||
|
||||
// code generation down here
|
||||
protected ResultSetToObject<K, T> genClass() {
|
||||
protected ResultSetToObject<K, T> genClass() throws IOException {
|
||||
final String className = "CompilingMapper";
|
||||
final String tType = typeFromName(_returnTypeClass);
|
||||
final String kType = typeFromName(_mapKeyType);
|
||||
@ -153,11 +161,12 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
final StringBuilder java = new StringBuilder(header);
|
||||
//java.append("return null;\n");
|
||||
gen(java, tType);
|
||||
java.append("return ret;\n");
|
||||
|
||||
java.append(" }\n\n public ").append(kType).append(" getFirstColumn(final java.sql.ResultSet rs, final java.util.Calendar cal) throws java.sql.SQLException {\n ");
|
||||
if(_mapKeyType != null){
|
||||
java.append("return ");
|
||||
extractColumnValueString(java, 1, _tmf.getTypeId(_mapKeyType));
|
||||
extractColumnValueString(java, 1, _mapKeyType);
|
||||
} else {
|
||||
java.append("throw new com.moparisthebest.jdbc.MapperException(com.moparisthebest.jdbc.CompilingRowToObjectMapper.firstColumnError)");
|
||||
}
|
||||
@ -167,10 +176,10 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
return compiler.compile(className, java);
|
||||
}
|
||||
|
||||
protected void gen(final StringBuilder java, final String tType) {
|
||||
public void gen(final Appendable java, final String tType) throws IOException {
|
||||
|
||||
if(mapOnlySecondColumn){
|
||||
java.append("return ");
|
||||
java.append("final ").append(tType).append(" ret = ");
|
||||
extractColumnValueString(java, 2, _tmf.getTypeId(_returnTypeClass));
|
||||
java.append(";\n");
|
||||
return;
|
||||
@ -179,7 +188,7 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
lazyLoadConstructor();
|
||||
|
||||
if (resultSetConstructor) {
|
||||
java.append("return new ").append(tType).append("(rs);\n");
|
||||
java.append("final ").append(tType).append(" ret = new ").append(tType).append("(rs);\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -197,8 +206,7 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
}
|
||||
} else // we want a generic object type
|
||||
for (int x = 1; x < columnLength; ++x)
|
||||
java.append("ret.put(").append(escapeMapKeyString(keys[x].toLowerCase())).append(", rs.getObject(").append(x).append("));\n");
|
||||
java.append("return ret;\n");
|
||||
java.append("ret.put(").append(escapeMapKeyString(keys[x].toLowerCase())).append(", rs.getObject(").append(String.valueOf(x)).append("));\n");
|
||||
return;
|
||||
} catch (Throwable e) {
|
||||
throw new MapperException(e.getClass().getName() + " when trying to create a Map<String, "
|
||||
@ -207,14 +215,13 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
}
|
||||
else if (componentType != null) // we want an array
|
||||
try {
|
||||
java.append("final ").append(tType).append(" ret = new ").append(tType.substring(0, tType.length() - 1)).append(_columnCount).append("];\n");
|
||||
java.append("final ").append(tType).append(" ret = new ").append(tType.substring(0, tType.length() - 1)).append(String.valueOf(_columnCount)).append("];\n");
|
||||
final int typeId = _tmf.getTypeId(componentType);
|
||||
for (int x = 0; x < _columnCount; ) {
|
||||
java.append("ret[").append(x).append("] = ");
|
||||
java.append("ret[").append(String.valueOf(x)).append("] = ");
|
||||
extractColumnValueString(java, ++x, typeId);
|
||||
java.append(";\n");
|
||||
}
|
||||
java.append("return ret;\n");
|
||||
return;
|
||||
} catch (Throwable e) {
|
||||
throw new MapperException(e.getClass().getName() + " when trying to create a "
|
||||
@ -230,7 +237,7 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
|
||||
try {
|
||||
if (typeId != TypeMappingsFactory.TYPE_UNKNOWN) {
|
||||
java.append("return ");
|
||||
java.append("final ").append(tType).append(" ret = ");
|
||||
extractColumnValueString(java, 1, typeId);
|
||||
java.append(";\n");
|
||||
return;
|
||||
@ -243,7 +250,7 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
}
|
||||
*/
|
||||
// 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(") ");
|
||||
java.append("final ").append(tType).append(" ret = (").append(tType).append(") ");
|
||||
extractColumnValueString(java, 1, typeId);
|
||||
java.append(";\n");
|
||||
return;
|
||||
@ -299,7 +306,11 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
// if this resultObject is Finishable, call finish()
|
||||
if (Finishable.class.isAssignableFrom(_returnTypeClass))
|
||||
java.append("ret.finish(rs);\n");
|
||||
java.append("return ret;\n");
|
||||
}
|
||||
|
||||
|
||||
public void extractColumnValueString(final Appendable java, final int index, final Class resultType) throws IOException {
|
||||
extractColumnValueString(java, index, _tmf.getTypeId(resultType));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -310,86 +321,86 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
* @return The extracted value
|
||||
* @throws java.sql.SQLException on error.
|
||||
*/
|
||||
protected void extractColumnValueString(final StringBuilder java, final int index, final int resultType) {
|
||||
public void extractColumnValueString(final Appendable java, final int index, final int resultType) throws IOException {
|
||||
switch (resultType) {
|
||||
case TypeMappingsFactory.TYPE_INT:
|
||||
java.append("rs.getInt(").append(index).append(")");
|
||||
java.append("rs.getInt(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_LONG:
|
||||
java.append("rs.getLong(").append(index).append(")");
|
||||
java.append("rs.getLong(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_FLOAT:
|
||||
java.append("rs.getFloat(").append(index).append(")");
|
||||
java.append("rs.getFloat(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_DOUBLE:
|
||||
java.append("rs.getDouble(").append(index).append(")");
|
||||
java.append("rs.getDouble(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_BYTE:
|
||||
java.append("rs.getByte(").append(index).append(")");
|
||||
java.append("rs.getByte(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_SHORT:
|
||||
java.append("rs.getInt(").append(index).append(")");
|
||||
java.append("rs.getInt(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_BOOLEAN:
|
||||
java.append("getBooleanYN(rs, ").append(index).append(")");
|
||||
java.append("getBooleanYN(rs, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_INT_OBJ:
|
||||
java.append("getObjectInt(rs, ").append(index).append(")");
|
||||
java.append("getObjectInt(rs, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_LONG_OBJ:
|
||||
java.append("getObjectLong(rs, ").append(index).append(")");
|
||||
java.append("getObjectLong(rs, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_FLOAT_OBJ:
|
||||
java.append("getObjectFloat(rs, ").append(index).append(")");
|
||||
java.append("getObjectFloat(rs, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_DOUBLE_OBJ:
|
||||
java.append("getObjectDouble(rs, ").append(index).append(")");
|
||||
java.append("getObjectDouble(rs, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_BYTE_OBJ:
|
||||
java.append("getObjectByte(rs, ").append(index).append(")");
|
||||
java.append("getObjectByte(rs, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_SHORT_OBJ:
|
||||
java.append("getObjectShort(rs, ").append(index).append(")");
|
||||
java.append("getObjectShort(rs, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_BOOLEAN_OBJ:
|
||||
java.append("getObjectBooleanYN(rs, ").append(index).append(")");
|
||||
java.append("getObjectBooleanYN(rs, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_STRING:
|
||||
case TypeMappingsFactory.TYPE_XMLBEAN_ENUM:
|
||||
java.append("rs.getString(").append(index).append(")");
|
||||
java.append("rs.getString(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_BIG_DECIMAL:
|
||||
java.append("rs.getBigDecimal(").append(index).append(")");
|
||||
java.append("rs.getBigDecimal(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_BYTES:
|
||||
java.append("rs.getBytes(").append(index).append(")");
|
||||
java.append("rs.getBytes(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_TIMESTAMP:
|
||||
java.append("getTimestamp(rs, cal, ").append(index).append(")");
|
||||
java.append("getTimestamp(rs, cal, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_TIME:
|
||||
java.append("getTime(rs, cal, ").append(index).append(")");
|
||||
java.append("getTime(rs, cal, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_SQLDATE:
|
||||
java.append("getSqlDate(rs, cal, ").append(index).append(")");
|
||||
java.append("getSqlDate(rs, cal, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_DATE:
|
||||
java.append("getUtilDate(rs, cal, ").append(index).append(")");
|
||||
java.append("getUtilDate(rs, cal, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_CALENDAR:
|
||||
java.append("getCalendar(rs, cal, ").append(index).append(")");
|
||||
java.append("getCalendar(rs, cal, ").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_REF:
|
||||
java.append("rs.getRef(").append(index).append(")");
|
||||
java.append("rs.getRef(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_BLOB:
|
||||
java.append("rs.getBlob(").append(index).append(")");
|
||||
java.append("rs.getBlob(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_CLOB:
|
||||
java.append("rs.getClob(").append(index).append(")");
|
||||
java.append("rs.getClob(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_ARRAY:
|
||||
java.append("rs.getArray(").append(index).append(")");
|
||||
java.append("rs.getArray(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
case TypeMappingsFactory.TYPE_READER:
|
||||
case TypeMappingsFactory.TYPE_STREAM:
|
||||
@ -397,7 +408,7 @@ public class CompilingRowToObjectMapper<K, T> extends RowToObjectMapper<K, T> {
|
||||
case TypeMappingsFactory.TYPE_STRUCT:
|
||||
case TypeMappingsFactory.TYPE_UNKNOWN:
|
||||
// JAVA_TYPE (could be any), or REF
|
||||
java.append("rs.getObject(").append(index).append(")");
|
||||
java.append("rs.getObject(").append(String.valueOf(index)).append(")");
|
||||
return;
|
||||
default:
|
||||
throw new MapperException("internal error: unknown type ID: " + Integer.toString(resultType));
|
||||
|
@ -95,6 +95,10 @@ public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
|
||||
this(resultSet, returnTypeClass, cal, mapValType, mapKeyType, false);
|
||||
}
|
||||
|
||||
public RowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType, boolean caseInsensitiveMap) {
|
||||
this(null, resultSet, returnTypeClass, cal, mapValType, mapKeyType, caseInsensitiveMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new RowToObjectMapper.
|
||||
*
|
||||
@ -102,8 +106,8 @@ public class RowToObjectMapper<K, T> extends AbstractRowMapper<K, T> {
|
||||
* @param returnTypeClass Class to map to.
|
||||
* @param cal Calendar instance for date/time mappings.
|
||||
*/
|
||||
public RowToObjectMapper(ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType, boolean caseInsensitiveMap) {
|
||||
super(resultSet, returnTypeClass, cal, mapKeyType);
|
||||
public RowToObjectMapper(String[] keys, ResultSet resultSet, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType, boolean caseInsensitiveMap) {
|
||||
super(keys, resultSet, returnTypeClass, cal, mapKeyType);
|
||||
returnMap = Map.class.isAssignableFrom(returnTypeClass);
|
||||
if(returnMap){
|
||||
Class<? extends T> rtc = ResultSetMapper.getConcreteClass(returnTypeClass, HashMap.class);
|
||||
|
@ -0,0 +1,184 @@
|
||||
package com.moparisthebest.jdbc.codegen;
|
||||
|
||||
import com.moparisthebest.jdbc.CompilingRowToObjectMapper;
|
||||
import com.moparisthebest.jdbc.ResultSetMapper;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.ArrayType;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.*;
|
||||
|
||||
import static com.moparisthebest.jdbc.codegen.JdbcMapperProcessor.typeMirrorToClass;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/26/17.
|
||||
*/
|
||||
public class CompileTimeResultSetMapper {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public void mapToResultType(final Writer w, final String[] keys, final ExecutableElement eeMethod, final int arrayMaxLength, final Calendar cal) throws IOException, NoSuchMethodException, ClassNotFoundException {
|
||||
final Method m = fromExecutableElement(eeMethod);
|
||||
final Class returnType = m.getReturnType();
|
||||
final TypeMirror returnTypeMirror = eeMethod.getReturnType();
|
||||
if (returnType.isArray()) {
|
||||
toArray(w, keys, ((ArrayType) returnTypeMirror).getComponentType(), returnType.getComponentType(), arrayMaxLength, cal);
|
||||
} else if (Collection.class.isAssignableFrom(returnType)) {
|
||||
toCollection(w, keys, returnTypeMirror, returnType, ((DeclaredType) returnTypeMirror).getTypeArguments().get(0), (Class) getActualTypeArguments(m)[0], arrayMaxLength, cal);
|
||||
} else if (Map.class.isAssignableFrom(returnType)) {
|
||||
final List<? extends TypeMirror> typeArguments = ((DeclaredType) returnTypeMirror).getTypeArguments();
|
||||
final Type[] types = getActualTypeArguments(m);
|
||||
if (types[1] instanceof ParameterizedType) { // for collectionMaps
|
||||
final ParameterizedType pt = (ParameterizedType) types[1];
|
||||
final Class collectionType = (Class) pt.getRawType();
|
||||
if (Collection.class.isAssignableFrom(collectionType)) {
|
||||
final TypeMirror collectionTypeMirror = typeArguments.get(1);
|
||||
toMapCollection(w, keys,
|
||||
returnTypeMirror, returnType,
|
||||
typeArguments.get(0), (Class) types[0],
|
||||
collectionTypeMirror, collectionType,
|
||||
((DeclaredType) collectionTypeMirror).getTypeArguments().get(0), (Class) pt.getActualTypeArguments()[0],
|
||||
arrayMaxLength, cal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
toMap(w, keys, returnTypeMirror, returnType, typeArguments.get(0), (Class) types[0], typeArguments.get(1), (Class) types[1], arrayMaxLength, cal);
|
||||
} else if (Iterator.class.isAssignableFrom(returnType)) {
|
||||
if (ListIterator.class.isAssignableFrom(returnType))
|
||||
toListIterator(w, keys, ((DeclaredType) returnTypeMirror).getTypeArguments().get(0), (Class) getActualTypeArguments(m)[0], arrayMaxLength, cal);
|
||||
else
|
||||
toIterator(w, keys, ((DeclaredType) returnTypeMirror).getTypeArguments().get(0), (Class) getActualTypeArguments(m)[0], arrayMaxLength, cal);
|
||||
} else {
|
||||
toObject(w, keys, returnTypeMirror, returnType, cal);
|
||||
}
|
||||
}
|
||||
|
||||
public <K, T> CompilingRowToObjectMapper<K, T> getRowMapper(final String[] keys, Class<T> returnTypeClass, Calendar cal, Class<?> mapValType, Class<K> mapKeyType) {
|
||||
return new CompilingRowToObjectMapper<K, T>(keys, returnTypeClass, cal, mapValType, mapKeyType);
|
||||
}
|
||||
|
||||
public void writeObject(final Writer w, final String[] keys, final TypeMirror returnTypeMirror, final Class<?> returnType, final Calendar cal) throws IOException {
|
||||
getRowMapper(keys, returnType, cal, null, null).gen(w, returnTypeMirror.toString());
|
||||
}
|
||||
|
||||
public void toObject(final Writer w, final String[] keys, final TypeMirror returnTypeMirror, final Class<?> returnType, final Calendar cal) throws IOException {
|
||||
w.write("\t\t\tif(rs.next()) {\n");
|
||||
writeObject(w, keys, returnTypeMirror, returnType, cal);
|
||||
w.write("\t\t\t\treturn ret;\n\t\t\t} else {\n\t\t\t\treturn null;\n\t\t\t}\n");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Collection<E>, E> void writeCollection(final Writer w, final String[] keys, final String returnTypeString, Class<T> collectionType, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
|
||||
collectionType = (Class<T>) ResultSetMapper.getConcreteClass(collectionType, ArrayList.class);
|
||||
w.write("\t\t\tfinal ");
|
||||
w.write(returnTypeString);
|
||||
w.write(" _colret = new ");
|
||||
w.write(collectionType.getCanonicalName());
|
||||
w.write(returnTypeString.substring(returnTypeString.indexOf('<')));
|
||||
w.write("();\n\t\t\twhile(rs.next()) {\n");
|
||||
writeObject(w, keys, componentTypeMirror, componentType, cal);
|
||||
w.write("\t\t\t\t_colret.add(ret);\n\t\t\t}\n");
|
||||
}
|
||||
|
||||
public <T extends Collection<E>, E> void toCollection(final Writer w, final String[] keys, final TypeMirror collectionTypeMirror, Class<T> collectionType, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
|
||||
writeCollection(w, keys, collectionTypeMirror.toString(), collectionType, componentTypeMirror, componentType, arrayMaxLength, cal);
|
||||
w.write("\t\t\treturn _colret;\n");
|
||||
}
|
||||
|
||||
public <E> void toArray(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
|
||||
final String returnTypeString = componentTypeMirror.toString();
|
||||
writeCollection(w, keys, "java.util.List<" + returnTypeString + ">", ArrayList.class, componentTypeMirror, componentType, arrayMaxLength, cal);
|
||||
w.write("\t\t\treturn _colret.toArray(new ");
|
||||
w.write(returnTypeString);
|
||||
w.write("[_colret.size()]);\n");
|
||||
}
|
||||
|
||||
public <E> void toListIterator(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
|
||||
final String returnTypeString = componentTypeMirror.toString();
|
||||
writeCollection(w, keys, "java.util.List<" + returnTypeString + ">", ArrayList.class, componentTypeMirror, componentType, arrayMaxLength, cal);
|
||||
w.write("\t\t\treturn _colret.listIterator();\n");
|
||||
}
|
||||
|
||||
public <E> void toIterator(final Writer w, final String[] keys, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
|
||||
final String returnTypeString = componentTypeMirror.toString();
|
||||
writeCollection(w, keys, "java.util.List<" + returnTypeString + ">", ArrayList.class, componentTypeMirror, componentType, arrayMaxLength, cal);
|
||||
w.write("\t\t\treturn _colret.iterator();\n");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Map<K, E>, K, E> void toMap(final Writer w, final String[] keys, final TypeMirror mapTypeMirror, Class<T> mapType, final TypeMirror mapKeyTypeMirror, Class<E> mapKeyType, final TypeMirror componentTypeMirror, Class<E> componentType, int arrayMaxLength, Calendar cal) throws IOException {
|
||||
mapType = (Class<T>) ResultSetMapper.getConcreteClass(mapType, HashMap.class);
|
||||
final String returnTypeString = mapTypeMirror.toString();
|
||||
w.write("\t\t\tfinal ");
|
||||
w.write(returnTypeString);
|
||||
w.write(" _colret = new ");
|
||||
w.write(mapType.getCanonicalName());
|
||||
w.write(returnTypeString.substring(returnTypeString.indexOf('<')));
|
||||
w.write("();\n\t\t\twhile(rs.next()) {\n");
|
||||
|
||||
//writeObject(w, keys, componentTypeMirror, componentType, cal);
|
||||
final CompilingRowToObjectMapper rm = getRowMapper(keys, componentType, cal, null, mapKeyType);
|
||||
rm.gen(w, componentTypeMirror.toString());
|
||||
w.write("\t\t\t\t_colret.put(");
|
||||
rm.extractColumnValueString(w, 1, mapKeyType);
|
||||
w.write(", ret);\n\t\t\t}\n");
|
||||
w.write("\t\t\treturn _colret;\n");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Map<K, E>, K, E extends Collection<C>, C> void toMapCollection(final Writer w, final String[] keys,
|
||||
final TypeMirror mapTypeMirror, Class<T> mapType,
|
||||
final TypeMirror mapKeyTypeMirror, Class<E> mapKeyType,
|
||||
final TypeMirror collectionTypeMirror, Class<E> collectionType,
|
||||
final TypeMirror componentTypeMirror, Class<E> componentType,
|
||||
int arrayMaxLength, Calendar cal) throws IOException {
|
||||
mapType = (Class<T>) ResultSetMapper.getConcreteClass(mapType, HashMap.class);
|
||||
collectionType = (Class<E>) ResultSetMapper.getConcreteClass(collectionType, ArrayList.class);
|
||||
final String returnTypeString = mapTypeMirror.toString();
|
||||
final String collectionTypeString = collectionTypeMirror.toString();
|
||||
w.write("\t\t\tfinal ");
|
||||
w.write(returnTypeString);
|
||||
w.write(" _colret = new ");
|
||||
w.write(mapType.getCanonicalName());
|
||||
w.write(returnTypeString.substring(returnTypeString.indexOf('<')));
|
||||
w.write("();\n\t\t\twhile(rs.next()) {\n");
|
||||
|
||||
//writeObject(w, keys, componentTypeMirror, componentType, cal);
|
||||
final CompilingRowToObjectMapper rm = getRowMapper(keys, componentType, cal, null, mapKeyType);
|
||||
rm.gen(w, componentTypeMirror.toString());
|
||||
|
||||
w.write("\t\t\t\tfinal ");
|
||||
w.write(mapKeyTypeMirror.toString());
|
||||
w.write(" _colkey = ");
|
||||
rm.extractColumnValueString(w, 1, mapKeyType);
|
||||
w.write(";\n\t\t\t\t");
|
||||
|
||||
w.write(collectionTypeString);
|
||||
w.write(" _collist = _colret.get(_colkey);\n\t\t\t\tif(_collist == null) {\n\t\t\t\t\t_collist = new ");
|
||||
w.write(collectionType.getCanonicalName());
|
||||
w.write(collectionTypeString.substring(collectionTypeString.indexOf('<')));
|
||||
w.write("();\n\t\t\t\t\t_colret.put(_colkey, _collist);\n\t\t\t\t}\n\t\t\t\t_collist.add(ret);\n\t\t\t}\n\t\t\treturn _colret;\n");
|
||||
}
|
||||
|
||||
private static Type[] getActualTypeArguments(Method m) {
|
||||
return ((ParameterizedType) m.getGenericReturnType()).getActualTypeArguments();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Method fromExecutableElement(final ExecutableElement eeMethod) throws ClassNotFoundException, NoSuchMethodException {
|
||||
final Class c = Class.forName(eeMethod.getEnclosingElement().asType().toString());
|
||||
final List<? extends VariableElement> params = eeMethod.getParameters();
|
||||
final Class<?>[] parameterTypes = new Class[params.size()];
|
||||
int count = -1;
|
||||
for (final VariableElement param : params) {
|
||||
parameterTypes[++count] = typeMirrorToClass(param.asType());
|
||||
}
|
||||
return c.getDeclaredMethod(eeMethod.getSimpleName().toString(), parameterTypes);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.moparisthebest.jdbc.codegen;
|
||||
|
||||
import com.moparisthebest.classgen.AbstractSQLParser;
|
||||
import com.moparisthebest.classgen.SQLParser;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/24/17.
|
||||
*/
|
||||
public interface JdbcMapper extends Closeable {
|
||||
|
||||
Connection getConnection();
|
||||
|
||||
@Override
|
||||
void close();
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.TYPE})
|
||||
public @interface Mapper {
|
||||
/**
|
||||
* The jndi name of the DataSource. This is a required element for this annotation.
|
||||
*/
|
||||
String jndiName() default "";
|
||||
|
||||
boolean cachePreparedStatements() default true;
|
||||
|
||||
Class<? extends SQLParser> sqlParser() default AbstractSQLParser.class;
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface SQL {
|
||||
/**
|
||||
* The SQL statement to send to the database. Required annotation element.
|
||||
*/
|
||||
String value();
|
||||
|
||||
OptionalBool cachePreparedStatement() default OptionalBool.INHERIT;
|
||||
|
||||
/**
|
||||
* Maximum array length.
|
||||
* Optional element.
|
||||
* This element has no effect on the call unless the method return type is an array.
|
||||
* When used in conjunction with the maxRows element, the size of the array generated
|
||||
* from the result set will be the smaller of maxRows and arrayMaxLength.
|
||||
* <p>
|
||||
* arrayMaxLength's default value is 1024, but may be set to zero to specify that
|
||||
* there is no size limit for the array generated from the ResultSet.
|
||||
* Since the generated array is stored in-memory, care should be taken when dealing
|
||||
* with very large ResultSets when the value of this element is set to zero.
|
||||
*/
|
||||
int arrayMaxLength() default -1;
|
||||
}
|
||||
|
||||
public enum OptionalBool {
|
||||
INHERIT,
|
||||
TRUE,
|
||||
FALSE;
|
||||
|
||||
public boolean combine(final boolean inherited) {
|
||||
switch (this) {
|
||||
case TRUE:
|
||||
return true;
|
||||
case FALSE:
|
||||
return false;
|
||||
}
|
||||
return inherited;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.moparisthebest.jdbc.codegen;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/24/17.
|
||||
*/
|
||||
public abstract class JdbcMapperFactory {
|
||||
|
||||
static final String SUFFIX = "Bean";
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends JdbcMapper> T create(final Class<T> jdbcMapper, final Connection connection) {
|
||||
try {
|
||||
return (T) Class.forName(jdbcMapper.getName() + SUFFIX).getConstructor(Connection.class).newInstance(connection);
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException("could not create JdbcMapper, did the processor run at compile time?", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends JdbcMapper> T create(final Class<T> jdbcMapper) {
|
||||
return create(jdbcMapper, null);
|
||||
}
|
||||
}
|
@ -0,0 +1,396 @@
|
||||
package com.moparisthebest.jdbc.codegen;
|
||||
|
||||
import com.moparisthebest.classgen.SQLParser;
|
||||
import com.moparisthebest.classgen.SimpleSQLParser;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.lang.model.util.Types;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.io.*;
|
||||
import java.sql.Blob;
|
||||
import java.sql.Clob;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.moparisthebest.jdbc.TryClose.tryClose;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/24/17.
|
||||
*/
|
||||
@SupportedAnnotationTypes("com.moparisthebest.jdbc.codegen.JdbcMapper.Mapper")
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_5)
|
||||
public class JdbcMapperProcessor extends AbstractProcessor {
|
||||
|
||||
private static final Pattern paramPattern = Pattern.compile("\\{([^}]+)\\}");
|
||||
private static final CompileTimeResultSetMapper rsm = new CompileTimeResultSetMapper();
|
||||
|
||||
private Types types;
|
||||
private TypeMirror sqlExceptionType, stringType, numberType, utilDateType, readerType, clobType,
|
||||
byteArrayType, inputStreamType, fileType, blobType
|
||||
//, clobStringType, blobStringType, arrayListObjectType
|
||||
;
|
||||
|
||||
public JdbcMapperProcessor() {
|
||||
//out.println("JdbcMapperProcessor running!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void init(final ProcessingEnvironment processingEnv) {
|
||||
super.init(processingEnv);
|
||||
this.types = processingEnv.getTypeUtils();
|
||||
final Elements elements = processingEnv.getElementUtils();
|
||||
sqlExceptionType = elements.getTypeElement(SQLException.class.getCanonicalName()).asType();
|
||||
stringType = elements.getTypeElement(String.class.getCanonicalName()).asType();
|
||||
numberType = elements.getTypeElement(Number.class.getCanonicalName()).asType();
|
||||
utilDateType = elements.getTypeElement(java.util.Date.class.getCanonicalName()).asType();
|
||||
readerType = elements.getTypeElement(Reader.class.getCanonicalName()).asType();
|
||||
clobType = elements.getTypeElement(Clob.class.getCanonicalName()).asType();
|
||||
inputStreamType = elements.getTypeElement(InputStream.class.getCanonicalName()).asType();
|
||||
fileType = elements.getTypeElement(File.class.getCanonicalName()).asType();
|
||||
blobType = elements.getTypeElement(Blob.class.getCanonicalName()).asType();
|
||||
// throws NPE:
|
||||
//byteArrayType = elements.getTypeElement(byte[].class.getCanonicalName()).asType();
|
||||
//byteArrayType = this.types.getArrayType(elements.getTypeElement(byte.class.getCanonicalName()).asType());
|
||||
//byteArrayType = elements.getTypeElement(byte.class.getCanonicalName()).asType();
|
||||
byteArrayType = types.getArrayType(types.getPrimitiveType(TypeKind.BYTE));
|
||||
/*
|
||||
clobStringType = elements.getTypeElement(ClobString.class.getCanonicalName()).asType();
|
||||
blobStringType = elements.getTypeElement(BlobString.class.getCanonicalName()).asType();
|
||||
arrayListObjectType = elements.getTypeElement(ArrayInList.ArrayListObject.class.getCanonicalName()).asType();
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
|
||||
//if(true) return false;
|
||||
if (annotations.isEmpty() || roundEnv.processingOver()) {
|
||||
// write out test classes
|
||||
return false;
|
||||
}
|
||||
if (isInitialized())
|
||||
//System.out.println("annotations: " + annotations);
|
||||
//System.out.println("roundEnv: " + roundEnv);
|
||||
for (final Element element : roundEnv.getElementsAnnotatedWith(JdbcMapper.Mapper.class))
|
||||
try {
|
||||
if (element.getKind() != ElementKind.CLASS && element.getKind() != ElementKind.INTERFACE && !element.getModifiers().contains(Modifier.ABSTRACT)) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.Mapper can only annotate an interface or abstract class", element);
|
||||
continue;
|
||||
}
|
||||
final TypeElement genClass = (TypeElement) element;
|
||||
final JdbcMapper.Mapper mapper = genClass.getAnnotation(JdbcMapper.Mapper.class);
|
||||
final SQLParser parser = new SimpleSQLParser();//(SQLParser)Class.forName(mapper.sqlParser().getCanonicalName()).newInstance();
|
||||
final String qualifiedName = genClass.getQualifiedName().toString();
|
||||
final boolean isInterface = genClass.getKind().isInterface();
|
||||
final boolean doJndi = !mapper.jndiName().isEmpty();
|
||||
Writer w = null;
|
||||
try {
|
||||
w = processingEnv.getFiler().createSourceFile(qualifiedName + JdbcMapperFactory.SUFFIX).openWriter();
|
||||
final String packageName = ((PackageElement) genClass.getEnclosingElement()).getQualifiedName().toString();
|
||||
final String className = genClass.getSimpleName() + JdbcMapperFactory.SUFFIX;
|
||||
if (!packageName.isEmpty()) {
|
||||
w.write("package ");
|
||||
w.write(packageName);
|
||||
w.write(";\n\n");
|
||||
}
|
||||
if (doJndi) {
|
||||
w.write("import javax.naming.InitialContext;\n");
|
||||
w.write("import javax.sql.DataSource;\n");
|
||||
}
|
||||
w.write("import java.sql.*;\n\n");
|
||||
w.write("import static com.moparisthebest.jdbc.TryClose.tryClose;\n\n");
|
||||
w.write("public class ");
|
||||
w.write(className);
|
||||
if (isInterface) {
|
||||
w.write(" implements ");
|
||||
} else {
|
||||
w.write(" extends ");
|
||||
}
|
||||
w.write(genClass.getSimpleName().toString());
|
||||
w.write(" {\n\n\tprivate final Connection conn;\n");
|
||||
if (doJndi) {
|
||||
w.write("\tprivate final InitialContext ctx;\n\n\tpublic ");
|
||||
w.write(className);
|
||||
w.write("() {\n\t\tthis(null);\n\t}\n");
|
||||
}
|
||||
w.write("\n\tpublic ");
|
||||
w.write(className);
|
||||
w.write("(Connection conn) {\n\t\t");
|
||||
if (doJndi) {
|
||||
w.write("InitialContext ctx = null;\n" +
|
||||
"\t\tif (conn == null)\n" +
|
||||
"\t\t\ttry {\n" +
|
||||
"\t\t\t\tctx = new InitialContext();\n" +
|
||||
"\t\t\t\tDataSource ds = (DataSource) ctx.lookup(\"");
|
||||
w.write(mapper.jndiName()); // todo: escape this? I don't think anyone needs that, for now...
|
||||
w.write("\");\n" +
|
||||
"\t\t\t\tconn = ds.getConnection();\n" +
|
||||
"\t\t\t} catch (Throwable e) {\n" +
|
||||
"\t\t\t\ttryClose(ctx);\n" +
|
||||
"\t\t\t\ttryClose(conn);\n" +
|
||||
"\t\t\t\tthrow new RuntimeException(e);\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\tthis.conn = conn;\n" +
|
||||
"\t\tthis.ctx = ctx;"
|
||||
);
|
||||
} else {
|
||||
w.write("this.conn = conn;");
|
||||
}
|
||||
w.write("\n\t\tif (this.conn == null)\n" +
|
||||
"\t\t\tthrow new NullPointerException(\"Connection needs to be non-null for JdbcMapper...\");\n\t}\n" +
|
||||
"\n\t@Override\n\tpublic Connection getConnection() {\n\t\treturn this.conn;\n\t}\n"
|
||||
);
|
||||
|
||||
// loop through methods
|
||||
final Types typeUtils = processingEnv.getTypeUtils();
|
||||
int cachedPreparedStatements = 0;
|
||||
for (final Element methodElement : genClass.getEnclosedElements()) {
|
||||
// can only implement abstract methods
|
||||
if (methodElement.getKind() != ElementKind.METHOD || !methodElement.getModifiers().contains(Modifier.ABSTRACT))
|
||||
continue;
|
||||
final ExecutableElement eeMethod = (ExecutableElement) methodElement;
|
||||
final JdbcMapper.SQL sql = eeMethod.getAnnotation(JdbcMapper.SQL.class);
|
||||
if (sql == null || sql.value().isEmpty()) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL with non-empty query is required on abstract or interface methods", methodElement);
|
||||
continue;
|
||||
}
|
||||
w.write("\n\t@Override\n\tpublic ");
|
||||
final String returnType = eeMethod.getReturnType().toString();
|
||||
w.write(returnType);
|
||||
w.write(" ");
|
||||
w.write(eeMethod.getSimpleName().toString());
|
||||
w.write('(');
|
||||
|
||||
// build query and bind param order
|
||||
final List<VariableElement> bindParams = new ArrayList<VariableElement>();
|
||||
final String sqlStatement;
|
||||
boolean sqlExceptionThrown = false;
|
||||
{
|
||||
// now parameters
|
||||
final List<? extends VariableElement> params = eeMethod.getParameters();
|
||||
final Map<String, VariableElement> paramMap = new HashMap<String, VariableElement>(params.size());
|
||||
final int numParams = params.size();
|
||||
int count = 0;
|
||||
for (final VariableElement param : params) {
|
||||
w.write("final ");
|
||||
w.write(param.asType().toString());
|
||||
w.write(' ');
|
||||
final String name = param.getSimpleName().toString();
|
||||
w.write(name);
|
||||
paramMap.put(name, param);
|
||||
if (++count != numParams)
|
||||
w.write(", ");
|
||||
}
|
||||
|
||||
// throws?
|
||||
w.write(")");
|
||||
final List<? extends TypeMirror> thrownTypes = eeMethod.getThrownTypes();
|
||||
final int numThrownTypes = thrownTypes.size();
|
||||
if (numThrownTypes > 0) {
|
||||
count = 0;
|
||||
w.write(" throws ");
|
||||
}
|
||||
for (final TypeMirror thrownType : thrownTypes) {
|
||||
sqlExceptionThrown |= typeUtils.isSameType(thrownType, sqlExceptionType);
|
||||
w.write(thrownType.toString());
|
||||
if (++count != numThrownTypes)
|
||||
w.write(", ");
|
||||
}
|
||||
w.write(" {\n");
|
||||
|
||||
final Matcher bindParamMatcher = paramPattern.matcher(sql.value());
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
while (bindParamMatcher.find()) {
|
||||
final String paramName = bindParamMatcher.group(1);
|
||||
final VariableElement bindParam = paramMap.get(paramName);
|
||||
if (bindParam == null) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@JdbcMapper.SQL sql has bind param '%s' not in method parameter list", paramName), methodElement);
|
||||
continue;
|
||||
}
|
||||
bindParams.add(bindParam);
|
||||
bindParamMatcher.appendReplacement(sb, "?");
|
||||
}
|
||||
bindParamMatcher.appendTail(sb);
|
||||
sqlStatement = sb.toString();
|
||||
}
|
||||
|
||||
final SQLParser parsedSQl = parser.parse(sqlStatement);
|
||||
// now implementation
|
||||
w.write("\t\tPreparedStatement ps = null;\n");
|
||||
if (parsedSQl.isSelect())
|
||||
w.write("\t\tResultSet rs = null;\n");
|
||||
w.write("\t\ttry {\n\t\t\tps = ");
|
||||
final boolean cachePreparedStatements = sql.cachePreparedStatement().combine(mapper.cachePreparedStatements());
|
||||
if (cachePreparedStatements) {
|
||||
w.write("this.prepareStatement(");
|
||||
w.write(Integer.toString(cachedPreparedStatements));
|
||||
w.write(", ");
|
||||
++cachedPreparedStatements;
|
||||
} else {
|
||||
w.write("conn.prepareStatement(");
|
||||
}
|
||||
w.write('"');
|
||||
w.write(sqlStatement);
|
||||
w.write("\");\n");
|
||||
|
||||
// now bind parameters
|
||||
int count = 0;
|
||||
for (final VariableElement param : bindParams)
|
||||
setObject(w, ++count, param.getSimpleName().toString(), param.asType());
|
||||
|
||||
if (!parsedSQl.isSelect()) {
|
||||
if (returnType.equals("void")) {
|
||||
w.write("\t\t\tps.executeUpdate();\n");
|
||||
} else if (returnType.equals("int") || returnType.equals("java.lang.Integer")) {
|
||||
w.write("\t\t\treturn ps.executeUpdate();\n");
|
||||
} else {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL sql other than SELECT must return either void, int, or Integer", methodElement);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
w.write("\t\t\trs = ps.executeQuery();\n");
|
||||
final String[] keys = parsedSQl.columnNames();
|
||||
if (keys == null || keys.length < 2) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL sql parsed no column names, proper SQL? Wildcard? or bad parser?", methodElement);
|
||||
continue;
|
||||
}
|
||||
for (final String key : keys)
|
||||
if ("*".equals(key)) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@JdbcMapper.SQL sql parsed a wildcard column name which is not supported", methodElement);
|
||||
return false;
|
||||
}
|
||||
rsm.mapToResultType(w, keys, eeMethod, sql.arrayMaxLength(), null);
|
||||
}
|
||||
|
||||
// if no SQLException is thrown, we have to catch it here and wrap it with RuntimeException...
|
||||
if (!sqlExceptionThrown) {
|
||||
w.write("\t\t} catch(SQLException e) {\n\t\t\tthrow new RuntimeException(e);\n");
|
||||
}
|
||||
|
||||
// close things
|
||||
w.write("\t\t} finally {\n");
|
||||
if (parsedSQl.isSelect())
|
||||
w.write("\t\t\ttryClose(rs);\n");
|
||||
if (!cachePreparedStatements)
|
||||
w.write("\t\t\ttryClose(ps);\n");
|
||||
w.write("\t\t}\n");
|
||||
|
||||
w.write("\t}\n");
|
||||
}
|
||||
|
||||
if (cachedPreparedStatements > 0) {
|
||||
w.write("\n\tprivate final PreparedStatement[] psCache = new PreparedStatement[");
|
||||
w.write(Integer.toString(cachedPreparedStatements));
|
||||
w.write("];\n\n\tprivate PreparedStatement prepareStatement(final int index, final String sql) throws SQLException {\n" +
|
||||
"\t\tfinal PreparedStatement ps = psCache[index];\n" +
|
||||
"\t\treturn ps == null ? (psCache[index] = conn.prepareStatement(sql)) : ps;\n" +
|
||||
"\t}\n");
|
||||
}
|
||||
|
||||
// close method
|
||||
w.write("\n\t@Override\n\tpublic void close() {\n\t\t");
|
||||
if (cachedPreparedStatements > 0)
|
||||
w.write("for(final PreparedStatement ps : psCache)\n\t\t\ttryClose(ps);\n\t\t");
|
||||
w.write("tryClose(conn);\n");
|
||||
if (doJndi)
|
||||
w.write("\t\ttryClose(ctx);\n");
|
||||
w.write("\t}\n");
|
||||
// end close method
|
||||
|
||||
w.write("}\n");
|
||||
} finally {
|
||||
tryClose(w);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), element);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setObject(final Writer w, final int index, String variableName, final TypeMirror o) throws SQLException, IOException {
|
||||
w.write("\t\t\t");
|
||||
// we are going to put most common ones up top so it should execute faster normally
|
||||
String method = null;
|
||||
// todo: avoid string concat here
|
||||
if (o.getKind().isPrimitive() || types.isAssignable(o, stringType) || types.isAssignable(o, numberType)) {
|
||||
method = "Object";
|
||||
// java.util.Date support, put it in a Timestamp
|
||||
} else if (types.isAssignable(o, utilDateType)) {
|
||||
method = "Object";
|
||||
// might need to wrap with Timestamp
|
||||
if (types.isSameType(o, utilDateType))
|
||||
variableName = "new java.sql.Timestamp(" + variableName + ".getTime())";
|
||||
// CLOB support
|
||||
} else if (types.isAssignable(o, readerType) || types.isAssignable(o, clobType)) {
|
||||
method = "Clob";
|
||||
/*
|
||||
} else if (o instanceof ClobString) {
|
||||
ps.setObject(index, ((ClobString) o).s == null ? null : ((ClobString) o).s);
|
||||
*/
|
||||
// BLOB support
|
||||
} else if (types.isAssignable(o, byteArrayType)) {
|
||||
method = "Blob";
|
||||
variableName = "new java.io.ByteArrayInputStream(" + variableName + ")";
|
||||
} else if (types.isAssignable(o, inputStreamType) || types.isAssignable(o, blobType)) {
|
||||
method = "Blob";
|
||||
} else if (types.isAssignable(o, fileType)) {
|
||||
// todo: does this close the InputStream properly????
|
||||
w.write("\t\t\ttry {\n" +
|
||||
"\t\t\t\tps.setBlob(" + index + ", new java.io.FileInputStream(" + variableName + "));\n" +
|
||||
"\t\t\t} catch (java.io.FileNotFoundException e) {\n" +
|
||||
"\t\t\t\tthrow new SQLException(\"File to Blob FileNotFoundException\", e);\n" +
|
||||
"\t\t\t}");
|
||||
return;
|
||||
/*
|
||||
} else if (o instanceof BlobString) {
|
||||
try {
|
||||
ps.setBlob(index, ((BlobString) o).s == null ? null : new ByteArrayInputStream(((BlobString) o).s.getBytes("UTF-8")));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new SQLException("String to Blob UnsupportedEncodingException", e);
|
||||
}
|
||||
} else if (o instanceof ArrayInList.ArrayListObject) {
|
||||
ps.setArray(index, ((ArrayInList.ArrayListObject) o).getArray());
|
||||
*/
|
||||
} else {
|
||||
// probably won't get here ever, but just in case...
|
||||
method = "Object";
|
||||
}
|
||||
w.write("ps.set");
|
||||
w.write(method);
|
||||
w.write('(');
|
||||
w.write(Integer.toString(index));
|
||||
w.write(", ");
|
||||
w.write(variableName);
|
||||
w.write(");\n");
|
||||
}
|
||||
|
||||
public static Class<?> typeMirrorToClass(final TypeMirror tm) throws ClassNotFoundException {
|
||||
switch (tm.getKind()) {
|
||||
case BOOLEAN:
|
||||
return boolean.class;
|
||||
case BYTE:
|
||||
return byte.class;
|
||||
case SHORT:
|
||||
return short.class;
|
||||
case INT:
|
||||
return int.class;
|
||||
case LONG:
|
||||
return long.class;
|
||||
case CHAR:
|
||||
return char.class;
|
||||
case FLOAT:
|
||||
return float.class;
|
||||
case DOUBLE:
|
||||
return double.class;
|
||||
default:
|
||||
return Class.forName(tm.toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
com.moparisthebest.jdbc.codegen.JdbcMapperProcessor
|
@ -0,0 +1,34 @@
|
||||
package com.moparisthebest.jdbc.codegen;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.moparisthebest.jdbc.QueryMapperTest.fieldPerson1;
|
||||
import static com.moparisthebest.jdbc.QueryMapperTest.getConnection;
|
||||
import static com.moparisthebest.jdbc.TryClose.tryClose;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/24/17.
|
||||
*/
|
||||
public class JdbcMapperTest {
|
||||
|
||||
private static PersonDAO dao;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() throws Throwable {
|
||||
dao = JdbcMapperFactory.create(PersonDAO.class, getConnection());
|
||||
//dao = new com.moparisthebest.jdbc.codegen.PersonDAOBean(getConnection());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() throws Throwable {
|
||||
tryClose(dao);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testName() throws Throwable {
|
||||
assertEquals(fieldPerson1.getFirstName(), dao.getFirstName(fieldPerson1.getPersonNo()));
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.moparisthebest.jdbc.codegen;
|
||||
|
||||
import com.moparisthebest.jdbc.dto.FieldPerson;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/24/17.
|
||||
*/
|
||||
@JdbcMapper.Mapper(
|
||||
// jndiName = "bob",
|
||||
cachePreparedStatements = false
|
||||
)
|
||||
public interface PersonDAO extends JdbcMapper {
|
||||
|
||||
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE last_name = {lastName}")
|
||||
int setFirstName(String firstName, String lastName);
|
||||
|
||||
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}")
|
||||
void setFirstName(String firstName, long personNo) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("UPDATE person SET first_name = {firstName} WHERE person_no = {personNo}")
|
||||
void setFirstNameBlob(byte[] firstName, long personNo) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name FROM person WHERE person_no = {personNo}")
|
||||
String getFirstName(long personNo) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name FROM person WHERE person_no = {personNo}")
|
||||
FieldPerson getPerson(long personNo) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name FROM person WHERE last_name = {lastName}")
|
||||
List<FieldPerson> getPeople(String lastName) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name FROM person WHERE last_name = {lastName}")
|
||||
FieldPerson[] getPeopleArray(String lastName) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name FROM person WHERE last_name = {lastName}")
|
||||
Iterator<FieldPerson> getPeopleIterator(String lastName) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name FROM person WHERE last_name = {lastName}")
|
||||
ListIterator<FieldPerson> getPeopleListIterator(String lastName) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name, person_no FROM person WHERE last_name = {lastName}")
|
||||
Map<String, FieldPerson> getPersonMap(String lastName) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name, last_name, person_no FROM person WHERE last_name = {lastName}")
|
||||
Map<String, List<FieldPerson>> getPersonMapList(String lastName) throws SQLException;
|
||||
|
||||
@JdbcMapper.SQL("SELECT first_name FROM person WHERE person_no = {personNo} and last_name = {lastName}")
|
||||
String getFirstName(long personNo, String lastName) throws SQLException;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.moparisthebest.jdbc.codegen;
|
||||
|
||||
import com.moparisthebest.classgen.SQLParser;
|
||||
import com.moparisthebest.classgen.SimpleSQLParser;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Created by mopar on 5/30/17.
|
||||
*/
|
||||
public class SqlParserTest {
|
||||
|
||||
private final SQLParser factory = new SimpleSQLParser();
|
||||
|
||||
@Test
|
||||
public void testSingleSelect() {
|
||||
final SQLParser ret = factory.parse("select bob from tom");
|
||||
assertTrue(ret.isSelect());
|
||||
assertArrayEquals(new String[]{null, "BOB"}, ret.columnNames());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiSelect() {
|
||||
final String[] expected = new String[]{null, "BOB", "TOM"};
|
||||
for (final String sql : new String[]{
|
||||
"select bob, tom from tom"
|
||||
, "select some_bob bob, some_tom as tom from tom"
|
||||
, "select tom.bob, some_tom as tom from tom"
|
||||
}) {
|
||||
final SQLParser ret = factory.parse(sql);
|
||||
assertTrue(ret.isSelect());
|
||||
assertArrayEquals(expected, ret.columnNames());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotSelect() {
|
||||
for (final String sql : new String[]{
|
||||
"UPDATE bob SET bob = 'bob' WHERE bob_no = 1"
|
||||
, "INSERT INTO bob (bob_no, bob) VALUES (1, 'bob')"
|
||||
, "MERGE INTO bob bla bla bla"
|
||||
}) {
|
||||
final SQLParser ret = factory.parse(sql);
|
||||
assertFalse(ret.isSelect());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user