aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/iciql/DaoProxy.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/iciql/DaoProxy.java')
-rw-r--r--src/main/java/com/iciql/DaoProxy.java1251
1 files changed, 625 insertions, 626 deletions
diff --git a/src/main/java/com/iciql/DaoProxy.java b/src/main/java/com/iciql/DaoProxy.java
index 34187c4..8851a0c 100644
--- a/src/main/java/com/iciql/DaoProxy.java
+++ b/src/main/java/com/iciql/DaoProxy.java
@@ -15,6 +15,11 @@
*/
package com.iciql;
+import com.iciql.Iciql.DataTypeAdapter;
+import com.iciql.util.JdbcUtils;
+import com.iciql.util.StringUtils;
+import com.iciql.util.Utils;
+
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
@@ -37,503 +42,497 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import com.iciql.Iciql.DataTypeAdapter;
-import com.iciql.util.JdbcUtils;
-import com.iciql.util.StringUtils;
-import com.iciql.util.Utils;
-
/**
* DaoProxy creates a dynamic instance of the provided Dao interface.
*
- * @author James Moger
- *
* @param <X>
+ * @author James Moger
*/
final class DaoProxy<X extends Dao> implements InvocationHandler, Dao {
- private final Db db;
+ private final Db db;
- private final Class<X> daoInterface;
+ private final Class<X> daoInterface;
- private final char bindingDelimiter = ':';
+ private final char bindingDelimiter = ':';
- private final Map<Method, IndexedSql> indexedSqlCache;
+ private final Map<Method, IndexedSql> indexedSqlCache;
- DaoProxy(Db db, Class<X> daoInterface) {
- this.db = db;
- this.daoInterface = daoInterface;
- this.indexedSqlCache = new ConcurrentHashMap<Method, IndexedSql>();
- }
+ DaoProxy(Db db, Class<X> daoInterface) {
+ this.db = db;
+ this.daoInterface = daoInterface;
+ this.indexedSqlCache = new ConcurrentHashMap<Method, IndexedSql>();
+ }
- /**
- * Builds a proxy object for the DAO interface.
- *
- * @return a proxy object
- */
- @SuppressWarnings("unchecked")
- X build() {
+ /**
+ * Builds a proxy object for the DAO interface.
+ *
+ * @return a proxy object
+ */
+ @SuppressWarnings("unchecked")
+ X build() {
- if (!daoInterface.isInterface()) {
- throw new IciqlException("Dao {0} must be an interface!", daoInterface.getName());
- }
+ if (!daoInterface.isInterface()) {
+ throw new IciqlException("Dao {0} must be an interface!", daoInterface.getName());
+ }
- ClassLoader classLoader = daoInterface.getClassLoader();
+ ClassLoader classLoader = daoInterface.getClassLoader();
- Set<Class<?>> interfaces = new HashSet<Class<?>>();
- interfaces.add(Dao.class);
- interfaces.add(daoInterface);
- for (Class<?> clazz : daoInterface.getInterfaces()) {
- interfaces.add(clazz);
- }
+ Set<Class<?>> interfaces = new HashSet<Class<?>>();
+ interfaces.add(Dao.class);
+ interfaces.add(daoInterface);
+ for (Class<?> clazz : daoInterface.getInterfaces()) {
+ interfaces.add(clazz);
+ }
- Class<?>[] constructorParams = { InvocationHandler.class };
- Class<?>[] allInterfaces = interfaces.toArray(new Class<?>[interfaces.size()]);
+ Class<?>[] constructorParams = {InvocationHandler.class};
+ Class<?>[] allInterfaces = interfaces.toArray(new Class<?>[interfaces.size()]);
- try {
+ try {
- Class<?> proxyClass = Proxy.getProxyClass(classLoader, allInterfaces);
- Constructor<?> proxyConstructor = proxyClass.getConstructor(constructorParams);
- return (X) proxyConstructor.newInstance(new Object[] { this });
+ Class<?> proxyClass = Proxy.getProxyClass(classLoader, allInterfaces);
+ Constructor<?> proxyConstructor = proxyClass.getConstructor(constructorParams);
+ return (X) proxyConstructor.newInstance(new Object[]{this});
- } catch (Exception e) {
- throw new IciqlException(e);
- }
- }
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
- /**
- * Invoke intercepts method calls and delegates execution to the appropriate object.
- */
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- try {
+ /**
+ * Invoke intercepts method calls and delegates execution to the appropriate object.
+ */
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
- if (method.getDeclaringClass() == Dao.class) {
+ if (method.getDeclaringClass() == Dao.class) {
- return method.invoke(this, args);
+ return method.invoke(this, args);
- } else if (method.isAnnotationPresent(SqlQuery.class)) {
+ } else if (method.isAnnotationPresent(SqlQuery.class)) {
- String sql = method.getAnnotation(SqlQuery.class).value();
- String statement = db.getDaoStatementProvider().getStatement(sql, db.getMode());
- return executeQuery(method, args, statement);
+ String sql = method.getAnnotation(SqlQuery.class).value();
+ String statement = db.getDaoStatementProvider().getStatement(sql, db.getMode());
+ return executeQuery(method, args, statement);
- } else if (method.isAnnotationPresent(SqlStatement.class)) {
+ } else if (method.isAnnotationPresent(SqlStatement.class)) {
- String sql = method.getAnnotation(SqlStatement.class).value();
- String statement = db.getDaoStatementProvider().getStatement(sql, db.getMode());
- return executeStatement(method, args, statement);
+ String sql = method.getAnnotation(SqlStatement.class).value();
+ String statement = db.getDaoStatementProvider().getStatement(sql, db.getMode());
+ return executeStatement(method, args, statement);
- } else {
+ } else {
- throw new IciqlException("Can not invoke non-dao method {0}.{1}",
- method.getDeclaringClass().getSimpleName(), method.getName());
+ throw new IciqlException("Can not invoke non-dao method {0}.{1}",
+ method.getDeclaringClass().getSimpleName(), method.getName());
- }
+ }
- } catch (InvocationTargetException te) {
- throw te.getCause();
- }
- }
+ } catch (InvocationTargetException te) {
+ throw te.getCause();
+ }
+ }
- /**
- * Execute a query.
- *
- * @param method
- * @param methodArgs
- * @param sql
- * @return the result
- */
- private Object executeQuery(Method method, Object[] methodArgs, String sql) {
+ /**
+ * Execute a query.
+ *
+ * @param method
+ * @param methodArgs
+ * @param sql
+ * @return the result
+ */
+ private Object executeQuery(Method method, Object[] methodArgs, String sql) {
/*
- * Determine and validate the return type
+ * Determine and validate the return type
*/
- Class<?> returnType = method.getReturnType();
-
- if (void.class == returnType) {
- throw new IciqlException("You must specify a return type for @{0} {1}.{2}!",
- SqlQuery.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName());
- }
-
- if (Collection.class.isAssignableFrom(returnType)) {
- throw new IciqlException("You may not return a collection for an @{0} method, please change the return type of {1}.{2} to YourClass[]!",
- SqlQuery.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName());
- }
-
- boolean isArray = false;
- if (returnType.isArray()) {
- isArray = true;
- returnType = returnType.getComponentType();
- }
-
- boolean isJavaType = returnType.isEnum()
- || returnType.isPrimitive()
- || java.lang.Boolean.class.isAssignableFrom(returnType)
- || java.lang.Number.class.isAssignableFrom(returnType)
- || java.lang.String.class.isAssignableFrom(returnType)
- || java.util.Date.class.isAssignableFrom(returnType)
- || byte[].class.isAssignableFrom(returnType);
-
- Class<? extends DataTypeAdapter<?>> adapter = Utils.getDataTypeAdapter(method.getAnnotations());
- if (adapter == null) {
- adapter = Utils.getDataTypeAdapter(returnType.getAnnotations());
- }
+ Class<?> returnType = method.getReturnType();
+
+ if (void.class == returnType) {
+ throw new IciqlException("You must specify a return type for @{0} {1}.{2}!",
+ SqlQuery.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName());
+ }
+
+ if (Collection.class.isAssignableFrom(returnType)) {
+ throw new IciqlException("You may not return a collection for an @{0} method, please change the return type of {1}.{2} to YourClass[]!",
+ SqlQuery.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName());
+ }
+
+ boolean isArray = false;
+ if (returnType.isArray()) {
+ isArray = true;
+ returnType = returnType.getComponentType();
+ }
+
+ boolean isJavaType = returnType.isEnum()
+ || returnType.isPrimitive()
+ || java.lang.Boolean.class.isAssignableFrom(returnType)
+ || java.lang.Number.class.isAssignableFrom(returnType)
+ || java.lang.String.class.isAssignableFrom(returnType)
+ || java.util.Date.class.isAssignableFrom(returnType)
+ || byte[].class.isAssignableFrom(returnType);
+
+ Class<? extends DataTypeAdapter<?>> adapter = Utils.getDataTypeAdapter(method.getAnnotations());
+ if (adapter == null) {
+ adapter = Utils.getDataTypeAdapter(returnType.getAnnotations());
+ }
/*
* Prepare & execute sql
*/
- PreparedSql preparedSql = prepareSql(method, methodArgs, sql);
+ PreparedSql preparedSql = prepareSql(method, methodArgs, sql);
- List<Object> objects;
- if (!isJavaType && adapter == null) {
+ List<Object> objects;
+ if (!isJavaType && adapter == null) {
- // query of an Iciql model
- objects = db.executeQuery(returnType, preparedSql.sql, preparedSql.parameters);
+ // query of an Iciql model
+ objects = db.executeQuery(returnType, preparedSql.sql, preparedSql.parameters);
- } else {
+ } else {
- // query of (array of) standard Java type or a DataTypeAdapter type
- objects = Utils.newArrayList();
- ResultSet rs = db.executeQuery(preparedSql.sql, preparedSql.parameters);
- try {
+ // query of (array of) standard Java type or a DataTypeAdapter type
+ objects = Utils.newArrayList();
+ ResultSet rs = db.executeQuery(preparedSql.sql, preparedSql.parameters);
+ try {
- while (rs.next()) {
+ while (rs.next()) {
- Object value = db.getDialect().deserialize(rs, 1, returnType, adapter);
- objects.add(value);
+ Object value = db.getDialect().deserialize(rs, 1, returnType, adapter);
+ objects.add(value);
- if (!isArray) {
- // we are not returning an array so we break
- // the loop and return the first result
- break;
- }
- }
+ if (!isArray) {
+ // we are not returning an array so we break
+ // the loop and return the first result
+ break;
+ }
+ }
- } catch (SQLException e) {
- throw new IciqlException(e);
- } finally {
- JdbcUtils.closeSilently(rs);
- }
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ } finally {
+ JdbcUtils.closeSilently(rs);
+ }
- }
+ }
/*
* Return the results
*/
- if (objects == null || objects.isEmpty()) {
+ if (objects == null || objects.isEmpty()) {
- // no results
- if (isArray) {
- // return an empty array
- return Array.newInstance(returnType, 0);
- }
+ // no results
+ if (isArray) {
+ // return an empty array
+ return Array.newInstance(returnType, 0);
+ }
- // nothing to return!
- return null;
+ // nothing to return!
+ return null;
- } else if (isArray) {
+ } else if (isArray) {
- // return an array of object results
- Object array = Array.newInstance(returnType, objects.size());
- for (int i = 0; i < objects.size(); i++) {
- Array.set(array, i, objects.get(i));
- }
- return array;
+ // return an array of object results
+ Object array = Array.newInstance(returnType, objects.size());
+ for (int i = 0; i < objects.size(); i++) {
+ Array.set(array, i, objects.get(i));
+ }
+ return array;
- }
+ }
- // return first element
- return objects.get(0);
- }
+ // return first element
+ return objects.get(0);
+ }
- /**
- * Execute a statement.
- *
- * @param method
- * @param methodArgs
- * @param sql
- * @return the result
- */
- private Object executeStatement(Method method, Object[] methodArgs, String sql) {
+ /**
+ * Execute a statement.
+ *
+ * @param method
+ * @param methodArgs
+ * @param sql
+ * @return the result
+ */
+ private Object executeStatement(Method method, Object[] methodArgs, String sql) {
/*
* Determine and validate the return type
*/
- Class<?> returnType = method.getReturnType();
+ Class<?> returnType = method.getReturnType();
- if (void.class != returnType && boolean.class != returnType && int.class != returnType) {
+ if (void.class != returnType && boolean.class != returnType && int.class != returnType) {
- throw new IciqlException("Invalid return type '{0}' for @{1} {2}.{3}!",
- returnType.getSimpleName(), SqlQuery.class.getSimpleName(),
- method.getDeclaringClass().getSimpleName(), method.getName());
- }
+ throw new IciqlException("Invalid return type '{0}' for @{1} {2}.{3}!",
+ returnType.getSimpleName(), SqlQuery.class.getSimpleName(),
+ method.getDeclaringClass().getSimpleName(), method.getName());
+ }
/*
* Prepare & execute sql
*/
- PreparedSql preparedSql = prepareSql(method, methodArgs, sql);
- int rows = db.executeUpdate(preparedSql.sql, preparedSql.parameters);
+ PreparedSql preparedSql = prepareSql(method, methodArgs, sql);
+ int rows = db.executeUpdate(preparedSql.sql, preparedSql.parameters);
/*
* Return the results
*/
- if (void.class == returnType) {
-
- // return nothing
- return null;
-
- } else if (boolean.class == returnType) {
-
- // return true if any rows were affected
- return rows > 0;
-
- } else {
-
- // return number of rows
- return rows;
-
- }
- }
-
- /**
- * Prepares an sql statement and execution parameters based on the supplied
- * method and it's arguments.
- *
- * @param method
- * @param methodArgs
- * @param sql
- * @return a prepared sql statement and arguments
- */
- private PreparedSql prepareSql(Method method, Object[] methodArgs, String sql) {
-
- if (methodArgs == null || methodArgs.length == 0) {
- // no method arguments
- return new PreparedSql(sql, null);
- }
-
- IndexedSql indexedSql = indexedSqlCache.get(method);
-
- if (indexedSql == null) {
-
- // index the sql and method args
- indexedSql = indexSql(method, sql);
-
- // cache the indexed sql for re-use
- indexedSqlCache.put(method, indexedSql);
- }
-
- final PreparedSql preparedSql = indexedSql.prepareSql(db, methodArgs);
- return preparedSql;
- }
-
- /**
- * Indexes an sql statement and method args based on the supplied
- * method and it's arguments.
- *
- * @param method
- * @param sql
- * @return an indexed sql statement and arguments
- */
- private IndexedSql indexSql(Method method, String sql) {
-
- Map<String, IndexedArgument> parameterIndex = buildParameterIndex(method);
-
- // build a regex to extract parameter names from the sql statement
- StringBuilder sb = new StringBuilder();
- sb.append(bindingDelimiter);
- sb.append("{1}(\\?");
- for (String name : parameterIndex.keySet()) {
- sb.append("|");
- // strip binding delimeter from name
- sb.append(name);
- }
- sb.append(')');
-
- // identify parameters, replace with the '?' PreparedStatement
- // delimiter and build the PreparedStatement parameters array
- final String regex = sb.toString();
- final Pattern p = Pattern.compile(regex);
- final Matcher m = p.matcher(sql);
- final StringBuffer buffer = new StringBuffer();
-
- List<IndexedArgument> indexedArgs = Utils.newArrayList();
- int count = 0;
- while (m.find()) {
- String binding = m.group(1);
- m.appendReplacement(buffer, "?");
-
- IndexedArgument indexedArg;
- if ("?".equals(binding)) {
- // standard ? JDBC placeholder
- indexedArg = parameterIndex.get("arg" + count);
- } else {
- // named placeholder
- indexedArg = parameterIndex.get(binding);
- }
-
- if (indexedArg == null) {
- throw new IciqlException("Unbound SQL parameter '{0}' in {1}.{2}",
- binding, method.getDeclaringClass().getSimpleName(), method.getName());
- }
- indexedArgs.add(indexedArg);
-
- count++;
- }
- m.appendTail(buffer);
-
- final String statement = buffer.toString();
-
- // create an IndexedSql container for the statement and indexes
- return new IndexedSql(statement, Collections.unmodifiableList(indexedArgs));
-
- }
-
- /**
- * Builds an index of parameter name->(position,typeAdapter) from the method arguments
- * array. This index is calculated once per method.
- *
- * @param method
- * @return a bindings map of ("name", IndexedArgument) pairs
- */
- private Map<String, IndexedArgument> buildParameterIndex(Method method) {
-
- Map<String, IndexedArgument> index = new TreeMap<String, IndexedArgument>();
-
- Annotation [][] annotationsMatrix = method.getParameterAnnotations();
- for (int i = 0; i < annotationsMatrix.length; i++) {
-
- Annotation [] annotations = annotationsMatrix[i];
+ if (void.class == returnType) {
+
+ // return nothing
+ return null;
+
+ } else if (boolean.class == returnType) {
+
+ // return true if any rows were affected
+ return rows > 0;
+
+ } else {
+
+ // return number of rows
+ return rows;
+
+ }
+ }
+
+ /**
+ * Prepares an sql statement and execution parameters based on the supplied
+ * method and it's arguments.
+ *
+ * @param method
+ * @param methodArgs
+ * @param sql
+ * @return a prepared sql statement and arguments
+ */
+ private PreparedSql prepareSql(Method method, Object[] methodArgs, String sql) {
+
+ if (methodArgs == null || methodArgs.length == 0) {
+ // no method arguments
+ return new PreparedSql(sql, null);
+ }
+
+ IndexedSql indexedSql = indexedSqlCache.get(method);
+
+ if (indexedSql == null) {
+
+ // index the sql and method args
+ indexedSql = indexSql(method, sql);
+
+ // cache the indexed sql for re-use
+ indexedSqlCache.put(method, indexedSql);
+ }
+
+ final PreparedSql preparedSql = indexedSql.prepareSql(db, methodArgs);
+ return preparedSql;
+ }
+
+ /**
+ * Indexes an sql statement and method args based on the supplied
+ * method and it's arguments.
+ *
+ * @param method
+ * @param sql
+ * @return an indexed sql statement and arguments
+ */
+ private IndexedSql indexSql(Method method, String sql) {
+
+ Map<String, IndexedArgument> parameterIndex = buildParameterIndex(method);
+
+ // build a regex to extract parameter names from the sql statement
+ StringBuilder sb = new StringBuilder();
+ sb.append(bindingDelimiter);
+ sb.append("{1}(\\?");
+ for (String name : parameterIndex.keySet()) {
+ sb.append("|");
+ // strip binding delimeter from name
+ sb.append(name);
+ }
+ sb.append(')');
+
+ // identify parameters, replace with the '?' PreparedStatement
+ // delimiter and build the PreparedStatement parameters array
+ final String regex = sb.toString();
+ final Pattern p = Pattern.compile(regex);
+ final Matcher m = p.matcher(sql);
+ final StringBuffer buffer = new StringBuffer();
+
+ List<IndexedArgument> indexedArgs = Utils.newArrayList();
+ int count = 0;
+ while (m.find()) {
+ String binding = m.group(1);
+ m.appendReplacement(buffer, "?");
+
+ IndexedArgument indexedArg;
+ if ("?".equals(binding)) {
+ // standard ? JDBC placeholder
+ indexedArg = parameterIndex.get("arg" + count);
+ } else {
+ // named placeholder
+ indexedArg = parameterIndex.get(binding);
+ }
+
+ if (indexedArg == null) {
+ throw new IciqlException("Unbound SQL parameter '{0}' in {1}.{2}",
+ binding, method.getDeclaringClass().getSimpleName(), method.getName());
+ }
+ indexedArgs.add(indexedArg);
+
+ count++;
+ }
+ m.appendTail(buffer);
+
+ final String statement = buffer.toString();
+
+ // create an IndexedSql container for the statement and indexes
+ return new IndexedSql(statement, Collections.unmodifiableList(indexedArgs));
+
+ }
+
+ /**
+ * Builds an index of parameter name->(position,typeAdapter) from the method arguments
+ * array. This index is calculated once per method.
+ *
+ * @param method
+ * @return a bindings map of ("name", IndexedArgument) pairs
+ */
+ private Map<String, IndexedArgument> buildParameterIndex(Method method) {
+
+ Map<String, IndexedArgument> index = new TreeMap<String, IndexedArgument>();
+
+ Annotation[][] annotationsMatrix = method.getParameterAnnotations();
+ for (int i = 0; i < annotationsMatrix.length; i++) {
+
+ Annotation[] annotations = annotationsMatrix[i];
/*
* Conditionally map the bean properties of the method argument
* class to Method and Field instances.
*/
- BindBean bean = getAnnotation(BindBean.class, annotations);
- if (bean != null) {
- final String prefix = bean.value();
- final Class<?> argumentClass = method.getParameterTypes()[i];
- Map<String, IndexedArgument> beanIndex = buildBeanIndex(i, prefix, argumentClass);
- index.putAll(beanIndex);
- }
-
- Class<? extends DataTypeAdapter<?>> typeAdapter = Utils.getDataTypeAdapter(annotations);
- final IndexedArgument indexedArgument = new IndexedArgument(i, typeAdapter);
-
- // :N - 1-indexed, like JDBC ResultSet
- index.put("" + (i + 1), indexedArgument);
-
- // argN - 0-indexed, like Reflection
- index.put("arg" + i, indexedArgument);
-
- // Bound name
- Bind binding = getAnnotation(Bind.class, annotations);
- if (binding!= null && !binding.value().isEmpty()) {
- index.put(binding.value(), indexedArgument);
- }
-
- // try mapping Java 8 argument names, may overwrite argN
- try {
- Class<?> nullArgs = null;
- Method getParameters = method.getClass().getMethod("getParameters", nullArgs);
- if (getParameters != null) {
- Object [] parameters = (Object []) getParameters.invoke(method, nullArgs);
- if (parameters != null) {
- Object o = parameters[i];
- Method getName = o.getClass().getMethod("getName", nullArgs);
- String j8name = getName.invoke(o, nullArgs).toString();
- if (!j8name.isEmpty()) {
- index.put(j8name, indexedArgument);
- }
- }
- }
- } catch (Throwable t) {
- }
- }
-
- return index;
- }
-
- /**
- * Builds an index of parameter name->(position,method) from the method arguments
- * array. This index is calculated once per method.
- *
- * @param argumentIndex
- * @param prefix
- * @param beanClass
- * @return a bindings map of ("prefix.property", IndexedArgument) pairs
- */
- private Map<String, IndexedArgument> buildBeanIndex(int argumentIndex, String prefix, Class<?> beanClass) {
-
- final String beanPrefix = StringUtils.isNullOrEmpty(prefix) ? "" : (prefix + ".");
- final Map<String, IndexedArgument> index = new TreeMap<String, IndexedArgument>();
-
- // map JavaBean property getters
- for (Method method : beanClass.getMethods()) {
-
- if (Modifier.isStatic(method.getModifiers())
- || method.getReturnType() == void.class
- || method.getParameterTypes().length > 0
- || method.getDeclaringClass() == Object.class) {
-
- // not a JavaBean property
- continue;
- }
-
- final String propertyName;
- final String name = method.getName();
- if (name.startsWith("get")) {
- propertyName = method.getName().substring(3);
- } else if (name.startsWith("is")) {
- propertyName = method.getName().substring(2);
- } else {
- propertyName = null;
- }
-
- if (propertyName == null) {
- // not a conventional JavaBean property
- continue;
- }
-
- final String binding = beanPrefix + preparePropertyName(propertyName);
- final IndexedArgument indexedArg = new IndexedArgument(argumentIndex, method);
-
- index.put(binding, indexedArg);
- }
-
- // map public instance fields
- for (Field field : beanClass.getFields()) {
-
- if (Modifier.isStatic(field.getModifiers())) {
- // not a JavaBean property
- continue;
- }
-
- final String binding = beanPrefix + preparePropertyName(field.getName());
- final IndexedArgument indexedArg = new IndexedArgument(argumentIndex, field);
-
- index.put(binding, indexedArg);
-
- }
-
- return index;
- }
-
- @SuppressWarnings("unchecked")
- private <T> T getAnnotation(Class<T> annotationClass, Annotation [] annotations) {
- if (annotations != null) {
- for (Annotation annotation : annotations) {
- if (annotation.annotationType() == annotationClass) {
- return (T) annotation;
- }
- }
- }
- return null;
- }
-
- private String preparePropertyName(String value) {
- return Character.toLowerCase(value.charAt(0)) + value.substring(1);
- }
+ BindBean bean = getAnnotation(BindBean.class, annotations);
+ if (bean != null) {
+ final String prefix = bean.value();
+ final Class<?> argumentClass = method.getParameterTypes()[i];
+ Map<String, IndexedArgument> beanIndex = buildBeanIndex(i, prefix, argumentClass);
+ index.putAll(beanIndex);
+ }
+
+ Class<? extends DataTypeAdapter<?>> typeAdapter = Utils.getDataTypeAdapter(annotations);
+ final IndexedArgument indexedArgument = new IndexedArgument(i, typeAdapter);
+
+ // :N - 1-indexed, like JDBC ResultSet
+ index.put("" + (i + 1), indexedArgument);
+
+ // argN - 0-indexed, like Reflection
+ index.put("arg" + i, indexedArgument);
+
+ // Bound name
+ Bind binding = getAnnotation(Bind.class, annotations);
+ if (binding != null && !binding.value().isEmpty()) {
+ index.put(binding.value(), indexedArgument);
+ }
+
+ // try mapping Java 8 argument names, may overwrite argN
+ try {
+ Class<?> nullArgs = null;
+ Method getParameters = method.getClass().getMethod("getParameters", nullArgs);
+ if (getParameters != null) {
+ Object[] parameters = (Object[]) getParameters.invoke(method, nullArgs);
+ if (parameters != null) {
+ Object o = parameters[i];
+ Method getName = o.getClass().getMethod("getName", nullArgs);
+ String j8name = getName.invoke(o, nullArgs).toString();
+ if (!j8name.isEmpty()) {
+ index.put(j8name, indexedArgument);
+ }
+ }
+ }
+ } catch (Throwable t) {
+ }
+ }
+
+ return index;
+ }
+
+ /**
+ * Builds an index of parameter name->(position,method) from the method arguments
+ * array. This index is calculated once per method.
+ *
+ * @param argumentIndex
+ * @param prefix
+ * @param beanClass
+ * @return a bindings map of ("prefix.property", IndexedArgument) pairs
+ */
+ private Map<String, IndexedArgument> buildBeanIndex(int argumentIndex, String prefix, Class<?> beanClass) {
+
+ final String beanPrefix = StringUtils.isNullOrEmpty(prefix) ? "" : (prefix + ".");
+ final Map<String, IndexedArgument> index = new TreeMap<String, IndexedArgument>();
+
+ // map JavaBean property getters
+ for (Method method : beanClass.getMethods()) {
+
+ if (Modifier.isStatic(method.getModifiers())
+ || method.getReturnType() == void.class
+ || method.getParameterTypes().length > 0
+ || method.getDeclaringClass() == Object.class) {
+
+ // not a JavaBean property
+ continue;
+ }
+
+ final String propertyName;
+ final String name = method.getName();
+ if (name.startsWith("get")) {
+ propertyName = method.getName().substring(3);
+ } else if (name.startsWith("is")) {
+ propertyName = method.getName().substring(2);
+ } else {
+ propertyName = null;
+ }
+
+ if (propertyName == null) {
+ // not a conventional JavaBean property
+ continue;
+ }
+
+ final String binding = beanPrefix + preparePropertyName(propertyName);
+ final IndexedArgument indexedArg = new IndexedArgument(argumentIndex, method);
+
+ index.put(binding, indexedArg);
+ }
+
+ // map public instance fields
+ for (Field field : beanClass.getFields()) {
+
+ if (Modifier.isStatic(field.getModifiers())) {
+ // not a JavaBean property
+ continue;
+ }
+
+ final String binding = beanPrefix + preparePropertyName(field.getName());
+ final IndexedArgument indexedArg = new IndexedArgument(argumentIndex, field);
+
+ index.put(binding, indexedArg);
+
+ }
+
+ return index;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> T getAnnotation(Class<T> annotationClass, Annotation[] annotations) {
+ if (annotations != null) {
+ for (Annotation annotation : annotations) {
+ if (annotation.annotationType() == annotationClass) {
+ return (T) annotation;
+ }
+ }
+ }
+ return null;
+ }
+
+ private String preparePropertyName(String value) {
+ return Character.toLowerCase(value.charAt(0)) + value.substring(1);
+ }
/*
*
@@ -541,213 +540,213 @@ final class DaoProxy<X extends Dao> implements InvocationHandler, Dao {
*
*/
- @Override
- public final Db db() {
- return db;
- }
-
- @Override
- public final <T> boolean insert(T t) {
- return db.insert(t);
- }
-
- @Override
- public final <T> void insertAll(List<T> t) {
- db.insertAll(t);
- }
-
- @Override
- public final <T> long insertAndGetKey(T t) {
- return db.insertAndGetKey(t);
- }
-
- @Override
- public final <T> List<Long> insertAllAndGetKeys(List<T> t) {
- return db.insertAllAndGetKeys(t);
- }
-
- @Override
- public final <T> boolean update(T t) {
- return db.update(t);
- }
-
- @Override
- public final <T> void updateAll(List<T> t) {
- db.updateAll(t);
- }
-
- @Override
- public final <T> void merge(T t) {
- db.merge(t);
- }
-
- @Override
- public final <T> boolean delete(T t) {
- return db.delete(t);
- }
-
- @Override
- public final <T> void deleteAll(List<T> t) {
- db.deleteAll(t);
- }
-
- @Override
- public final void close() {
- db.close();
- }
-
- /**
- * Container class to hold the prepared JDBC SQL statement and execution
- * parameters.
- */
- private class PreparedSql {
- final String sql;
- final Object [] parameters;
-
- PreparedSql(String sql, Object [] parameters) {
- this.sql = sql;
- this.parameters = parameters;
- }
-
- @Override
- public String toString() {
- return sql;
- }
-
- }
-
- /**
- * Container class to hold a parsed JDBC SQL statement and
- * IndexedParameters.
- * <p>
- * Instances of this class are cached because they are functional processing
- * containers as they contain Method and Field references for binding beans
- * and matching to method arguments.
- * </p>
- */
- private class IndexedSql {
- final String sql;
- final List<IndexedArgument> indexedArgs;
-
- IndexedSql(String sql, List<IndexedArgument> indexedArgs) {
- this.sql = sql;
- this.indexedArgs = indexedArgs;
- }
-
- /**
- * Prepares the method arguments for statement execution.
- *
- * @param db
- * @param methodArgs
- * @return the prepared sql statement and parameters
- */
- PreparedSql prepareSql(Db db, Object [] methodArgs) {
-
- Object [] parameters = new Object[indexedArgs.size()];
-
- for (int i = 0; i < indexedArgs.size(); i++) {
-
- IndexedArgument indexedArg = indexedArgs.get(i);
- Object methodArg = methodArgs[indexedArg.index];
+ @Override
+ public final Db db() {
+ return db;
+ }
+
+ @Override
+ public final <T> boolean insert(T t) {
+ return db.insert(t);
+ }
+
+ @Override
+ public final <T> void insertAll(List<T> t) {
+ db.insertAll(t);
+ }
+
+ @Override
+ public final <T> long insertAndGetKey(T t) {
+ return db.insertAndGetKey(t);
+ }
+
+ @Override
+ public final <T> List<Long> insertAllAndGetKeys(List<T> t) {
+ return db.insertAllAndGetKeys(t);
+ }
+
+ @Override
+ public final <T> boolean update(T t) {
+ return db.update(t);
+ }
+
+ @Override
+ public final <T> void updateAll(List<T> t) {
+ db.updateAll(t);
+ }
+
+ @Override
+ public final <T> void merge(T t) {
+ db.merge(t);
+ }
+
+ @Override
+ public final <T> boolean delete(T t) {
+ return db.delete(t);
+ }
+
+ @Override
+ public final <T> void deleteAll(List<T> t) {
+ db.deleteAll(t);
+ }
+
+ @Override
+ public final void close() {
+ db.close();
+ }
+
+ /**
+ * Container class to hold the prepared JDBC SQL statement and execution
+ * parameters.
+ */
+ private class PreparedSql {
+ final String sql;
+ final Object[] parameters;
+
+ PreparedSql(String sql, Object[] parameters) {
+ this.sql = sql;
+ this.parameters = parameters;
+ }
+
+ @Override
+ public String toString() {
+ return sql;
+ }
+
+ }
+
+ /**
+ * Container class to hold a parsed JDBC SQL statement and
+ * IndexedParameters.
+ * <p>
+ * Instances of this class are cached because they are functional processing
+ * containers as they contain Method and Field references for binding beans
+ * and matching to method arguments.
+ * </p>
+ */
+ private class IndexedSql {
+ final String sql;
+ final List<IndexedArgument> indexedArgs;
+
+ IndexedSql(String sql, List<IndexedArgument> indexedArgs) {
+ this.sql = sql;
+ this.indexedArgs = indexedArgs;
+ }
+
+ /**
+ * Prepares the method arguments for statement execution.
+ *
+ * @param db
+ * @param methodArgs
+ * @return the prepared sql statement and parameters
+ */
+ PreparedSql prepareSql(Db db, Object[] methodArgs) {
+
+ Object[] parameters = new Object[indexedArgs.size()];
+
+ for (int i = 0; i < indexedArgs.size(); i++) {
+
+ IndexedArgument indexedArg = indexedArgs.get(i);
+ Object methodArg = methodArgs[indexedArg.index];
+
+ Object value = methodArg;
+ Class<? extends DataTypeAdapter<?>> typeAdapter = indexedArg.typeAdapter;
+
+ if (indexedArg.method != null) {
+
+ // execute the bean method
+ try {
+
+ value = indexedArg.method.invoke(methodArg);
+ typeAdapter = Utils.getDataTypeAdapter(indexedArg.method.getAnnotations());
+
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+
+ } else if (indexedArg.field != null) {
+
+ // extract the field value
+ try {
+
+ value = indexedArg.field.get(methodArg);
+ typeAdapter = Utils.getDataTypeAdapter(indexedArg.field.getAnnotations());
+
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+
+ } else if (typeAdapter == null) {
+
+ // identify the type adapter for the argument class
+ typeAdapter = Utils.getDataTypeAdapter(methodArg.getClass().getAnnotations());
+ }
+
+ // prepare the parameter
+ parameters[i] = db.getDialect().serialize(value, typeAdapter);
+
+ }
+
+ return new PreparedSql(sql, parameters);
+
+ }
+
+ @Override
+ public String toString() {
+ return sql;
+ }
+ }
+
+ /**
+ * IndexedArgument holds cached information about how to process an method
+ * argument by it's index in the method arguments array.
+ * <p>
+ * An argument may be passed-through, might be bound to a bean property,
+ * might be transformed with a type adapter, or a combination of these.
+ * </p>
+ */
+ private class IndexedArgument {
+ final int index;
+ final Class<? extends DataTypeAdapter<?>> typeAdapter;
+ final Method method;
+ final Field field;
+
+ IndexedArgument(int index, Class<? extends DataTypeAdapter<?>> typeAdapter) {
+ this.index = index;
+ this.typeAdapter = typeAdapter;
+ this.method = null;
+ this.field = null;
+ }
+
+ IndexedArgument(int methodArgIndex, Method method) {
+ this.index = methodArgIndex;
+ this.typeAdapter = null;
+ this.method = method;
+ this.field = null;
+ }
+
+ IndexedArgument(int methodArgIndex, Field field) {
+ this.index = methodArgIndex;
+ this.typeAdapter = null;
+ this.method = null;
+ this.field = field;
+ }
+
+ @Override
+ public String toString() {
+
+ String accessor;
+ if (method != null) {
+ accessor = "M:" + method.getDeclaringClass().getSimpleName() + "." + method.getName();
+ } else if (field != null) {
+ accessor = "F:" + field.getDeclaringClass().getSimpleName() + "." + field.getName();
+ } else {
+ accessor = "A:arg";
+ }
- Object value = methodArg;
- Class<? extends DataTypeAdapter<?>> typeAdapter = indexedArg.typeAdapter;
-
- if (indexedArg.method != null) {
-
- // execute the bean method
- try {
-
- value = indexedArg.method.invoke(methodArg);
- typeAdapter = Utils.getDataTypeAdapter(indexedArg.method.getAnnotations());
-
- } catch (Exception e) {
- throw new IciqlException(e);
- }
-
- } else if (indexedArg.field != null) {
-
- // extract the field value
- try {
-
- value = indexedArg.field.get(methodArg);
- typeAdapter = Utils.getDataTypeAdapter(indexedArg.field.getAnnotations());
-
- } catch (Exception e) {
- throw new IciqlException(e);
- }
-
- } else if (typeAdapter == null) {
-
- // identify the type adapter for the argument class
- typeAdapter = Utils.getDataTypeAdapter(methodArg.getClass().getAnnotations());
- }
-
- // prepare the parameter
- parameters[i] = db.getDialect().serialize(value, typeAdapter);
-
- }
-
- return new PreparedSql(sql, parameters);
-
- }
-
- @Override
- public String toString() {
- return sql;
- }
- }
-
- /**
- * IndexedArgument holds cached information about how to process an method
- * argument by it's index in the method arguments array.
- * <p>
- * An argument may be passed-through, might be bound to a bean property,
- * might be transformed with a type adapter, or a combination of these.
- * </p>
- */
- private class IndexedArgument {
- final int index;
- final Class<? extends DataTypeAdapter<?>> typeAdapter;
- final Method method;
- final Field field;
-
- IndexedArgument(int index, Class<? extends DataTypeAdapter<?>> typeAdapter) {
- this.index = index;
- this.typeAdapter = typeAdapter;
- this.method = null;
- this.field = null;
- }
-
- IndexedArgument(int methodArgIndex, Method method) {
- this.index = methodArgIndex;
- this.typeAdapter = null;
- this.method = method;
- this.field = null;
- }
-
- IndexedArgument(int methodArgIndex, Field field) {
- this.index = methodArgIndex;
- this.typeAdapter = null;
- this.method = null;
- this.field = field;
- }
-
- @Override
- public String toString() {
-
- String accessor;
- if (method != null) {
- accessor = "M:" + method.getDeclaringClass().getSimpleName() + "." + method.getName();
- } else if (field != null) {
- accessor = "F:" + field.getDeclaringClass().getSimpleName() + "." + field.getName();
- } else {
- accessor = "A:arg";
- }
-
- return index + ":" + accessor + (typeAdapter == null ? "" : (":" + typeAdapter.getSimpleName()));
- }
-
- }
+ return index + ":" + accessor + (typeAdapter == null ? "" : (":" + typeAdapter.getSimpleName()));
+ }
+
+ }
}