You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DaoProxy.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. /*
  2. * Copyright 2014 James Moger.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.iciql;
  17. import java.lang.annotation.Annotation;
  18. import java.lang.reflect.Array;
  19. import java.lang.reflect.Constructor;
  20. import java.lang.reflect.Field;
  21. import java.lang.reflect.InvocationHandler;
  22. import java.lang.reflect.InvocationTargetException;
  23. import java.lang.reflect.Method;
  24. import java.lang.reflect.Modifier;
  25. import java.lang.reflect.Proxy;
  26. import java.sql.ResultSet;
  27. import java.sql.SQLException;
  28. import java.util.Collection;
  29. import java.util.Collections;
  30. import java.util.HashSet;
  31. import java.util.List;
  32. import java.util.Map;
  33. import java.util.Set;
  34. import java.util.TreeMap;
  35. import java.util.concurrent.ConcurrentHashMap;
  36. import java.util.regex.Matcher;
  37. import java.util.regex.Pattern;
  38. import com.iciql.Iciql.DataTypeAdapter;
  39. import com.iciql.Iciql.TypeAdapter;
  40. import com.iciql.util.JdbcUtils;
  41. import com.iciql.util.StringUtils;
  42. import com.iciql.util.Utils;
  43. /**
  44. * DaoProxy creates a dynamic instance of the provided Dao interface.
  45. *
  46. * @author James Moger
  47. *
  48. * @param <X>
  49. */
  50. final class DaoProxy<X extends Dao> implements InvocationHandler, Dao {
  51. private final Db db;
  52. private final Class<X> daoInterface;
  53. private final char bindingDelimiter = ':';
  54. private final Map<Method, IndexedSql> indexedSqlCache;
  55. DaoProxy(Db db, Class<X> daoInterface) {
  56. this.db = db;
  57. this.daoInterface = daoInterface;
  58. this.indexedSqlCache = new ConcurrentHashMap<Method, IndexedSql>();
  59. }
  60. /**
  61. * Builds a proxy object for the DAO interface.
  62. *
  63. * @return a proxy object
  64. */
  65. @SuppressWarnings("unchecked")
  66. X build() {
  67. if (!daoInterface.isInterface()) {
  68. throw new IciqlException("Dao {0} must be an interface!", daoInterface.getName());
  69. }
  70. ClassLoader classLoader = daoInterface.getClassLoader();
  71. Set<Class<?>> interfaces = new HashSet<Class<?>>();
  72. interfaces.add(Dao.class);
  73. interfaces.add(daoInterface);
  74. for (Class<?> clazz : daoInterface.getInterfaces()) {
  75. interfaces.add(clazz);
  76. }
  77. Class<?>[] constructorParams = { InvocationHandler.class };
  78. Class<?>[] allInterfaces = interfaces.toArray(new Class<?>[interfaces.size()]);
  79. try {
  80. Class<?> proxyClass = Proxy.getProxyClass(classLoader, allInterfaces);
  81. Constructor<?> proxyConstructor = proxyClass.getConstructor(constructorParams);
  82. return (X) proxyConstructor.newInstance(new Object[] { this });
  83. } catch (Exception e) {
  84. throw new IciqlException(e);
  85. }
  86. }
  87. /**
  88. * Invoke intercepts method calls and delegates execution to the appropriate object.
  89. */
  90. @Override
  91. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  92. try {
  93. if (method.getDeclaringClass() == Dao.class) {
  94. return method.invoke(this, args);
  95. } else if (method.isAnnotationPresent(SqlQuery.class)) {
  96. String sql = method.getAnnotation(SqlQuery.class).value();
  97. String statement = db.getDaoStatementProvider().getStatement(sql, db.getMode());
  98. return executeQuery(method, args, statement);
  99. } else if (method.isAnnotationPresent(SqlStatement.class)) {
  100. String sql = method.getAnnotation(SqlStatement.class).value();
  101. String statement = db.getDaoStatementProvider().getStatement(sql, db.getMode());
  102. return executeStatement(method, args, statement);
  103. } else {
  104. throw new IciqlException("Can not invoke non-dao method {0}.{1}",
  105. method.getDeclaringClass().getSimpleName(), method.getName());
  106. }
  107. } catch (InvocationTargetException te) {
  108. throw te.getCause();
  109. }
  110. }
  111. /**
  112. * Execute a query.
  113. *
  114. * @param method
  115. * @param methodArgs
  116. * @param sql
  117. * @return the result
  118. */
  119. private Object executeQuery(Method method, Object[] methodArgs, String sql) {
  120. /*
  121. * Determine and validate the return type
  122. */
  123. Class<?> returnType = method.getReturnType();
  124. if (void.class == returnType) {
  125. throw new IciqlException("You must specify a return type for @{0} {1}.{2}!",
  126. SqlQuery.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName());
  127. }
  128. if (Collection.class.isAssignableFrom(returnType)) {
  129. throw new IciqlException("You may not return a collection for an @{0} method, please change the return type of {1}.{2} to YourClass[]!",
  130. SqlQuery.class.getSimpleName(), method.getDeclaringClass().getSimpleName(), method.getName());
  131. }
  132. boolean isArray = false;
  133. if (returnType.isArray()) {
  134. isArray = true;
  135. returnType = returnType.getComponentType();
  136. }
  137. boolean isJavaType = returnType.isEnum()
  138. || returnType.isPrimitive()
  139. || java.lang.Boolean.class.isAssignableFrom(returnType)
  140. || java.lang.Number.class.isAssignableFrom(returnType)
  141. || java.lang.String.class.isAssignableFrom(returnType)
  142. || java.util.Date.class.isAssignableFrom(returnType)
  143. || byte[].class.isAssignableFrom(returnType);
  144. // determine the return type adapter, if any
  145. DataTypeAdapter<?> adapter = null;
  146. for (Annotation annotation : method.getAnnotations()) {
  147. if (annotation.annotationType() == TypeAdapter.class) {
  148. TypeAdapter typeAdapter = (TypeAdapter) annotation;
  149. adapter = db.getDialect().getAdapter(typeAdapter.value());
  150. break;
  151. } else if (annotation.annotationType().isAnnotationPresent(TypeAdapter.class)) {
  152. TypeAdapter typeAdapter = annotation.annotationType().getAnnotation(TypeAdapter.class);
  153. adapter = db.getDialect().getAdapter(typeAdapter.value());
  154. break;
  155. }
  156. }
  157. /*
  158. * Prepare & execute sql
  159. */
  160. PreparedSql preparedSql = prepareSql(method, methodArgs, sql);
  161. List<Object> objects;
  162. if (!isJavaType && adapter == null) {
  163. // query of an Iciql model
  164. objects = db.executeQuery(returnType, preparedSql.sql, preparedSql.parameters);
  165. } else {
  166. // query of (array of) standard Java type or a DataTypeAdapter type
  167. objects = Utils.newArrayList();
  168. ResultSet rs = db.executeQuery(preparedSql.sql, preparedSql.parameters);
  169. try {
  170. while (rs.next()) {
  171. Object o = rs.getObject(1);
  172. Object value;
  173. if (adapter == null) {
  174. // use internal Iciql type conversion
  175. value = Utils.convert(o, returnType);
  176. } else {
  177. // use the type adapter to convert the JDBC object to a domain type
  178. value = adapter.deserialize(o);
  179. }
  180. objects.add(value);
  181. if (!isArray) {
  182. // we are not returning an array so we break
  183. // the loop and return the first result
  184. break;
  185. }
  186. }
  187. } catch (SQLException e) {
  188. throw new IciqlException(e);
  189. } finally {
  190. JdbcUtils.closeSilently(rs);
  191. }
  192. }
  193. /*
  194. * Return the results
  195. */
  196. if (objects == null || objects.isEmpty()) {
  197. // no results
  198. if (isArray) {
  199. // return an empty array
  200. return Array.newInstance(returnType, 0);
  201. }
  202. // nothing to return!
  203. return null;
  204. } else if (isArray) {
  205. // return an array of object results
  206. Object array = Array.newInstance(returnType, objects.size());
  207. for (int i = 0; i < objects.size(); i++) {
  208. Array.set(array, i, objects.get(i));
  209. }
  210. return array;
  211. }
  212. // return first element
  213. return objects.get(0);
  214. }
  215. /**
  216. * Execute a statement.
  217. *
  218. * @param method
  219. * @param methodArgs
  220. * @param sql
  221. * @return the result
  222. */
  223. private Object executeStatement(Method method, Object[] methodArgs, String sql) {
  224. /*
  225. * Determine and validate the return type
  226. */
  227. Class<?> returnType = method.getReturnType();
  228. if (void.class != returnType && boolean.class != returnType && int.class != returnType) {
  229. throw new IciqlException("Invalid return type '{0}' for @{1} {2}.{3}!",
  230. returnType.getSimpleName(), SqlQuery.class.getSimpleName(),
  231. method.getDeclaringClass().getSimpleName(), method.getName());
  232. }
  233. /*
  234. * Prepare & execute sql
  235. */
  236. PreparedSql preparedSql = prepareSql(method, methodArgs, sql);
  237. int rows = db.executeUpdate(preparedSql.sql, preparedSql.parameters);
  238. /*
  239. * Return the results
  240. */
  241. if (void.class == returnType) {
  242. // return nothing
  243. return null;
  244. } else if (boolean.class == returnType) {
  245. // return true if any rows were affected
  246. return rows > 0;
  247. } else {
  248. // return number of rows
  249. return rows;
  250. }
  251. }
  252. /**
  253. * Prepares an sql statement and execution parameters based on the supplied
  254. * method and it's arguments.
  255. *
  256. * @param method
  257. * @param methodArgs
  258. * @param sql
  259. * @return a prepared sql statement and arguments
  260. */
  261. private PreparedSql prepareSql(Method method, Object[] methodArgs, String sql) {
  262. if (methodArgs == null || methodArgs.length == 0) {
  263. // no method arguments
  264. return new PreparedSql(sql, null);
  265. }
  266. IndexedSql indexedSql = indexedSqlCache.get(method);
  267. if (indexedSql == null) {
  268. // index the sql and method args
  269. indexedSql = indexSql(method, sql);
  270. // cache the indexed sql for re-use
  271. indexedSqlCache.put(method, indexedSql);
  272. }
  273. final PreparedSql preparedSql = indexedSql.prepareSql(db, methodArgs);
  274. return preparedSql;
  275. }
  276. /**
  277. * Indexes an sql statement and method args based on the supplied
  278. * method and it's arguments.
  279. *
  280. * @param method
  281. * @param sql
  282. * @return an indexed sql statement and arguments
  283. */
  284. private IndexedSql indexSql(Method method, String sql) {
  285. Map<String, IndexedArgument> parameterIndex = buildParameterIndex(method);
  286. // build a regex to extract parameter names from the sql statement
  287. StringBuilder sb = new StringBuilder();
  288. sb.append(bindingDelimiter);
  289. sb.append("{1}(\\?");
  290. for (String name : parameterIndex.keySet()) {
  291. sb.append("|");
  292. // strip binding delimeter from name
  293. sb.append(name);
  294. }
  295. sb.append(')');
  296. // identify parameters, replace with the '?' PreparedStatement
  297. // delimiter and build the PreparedStatement parameters array
  298. final String regex = sb.toString();
  299. final Pattern p = Pattern.compile(regex);
  300. final Matcher m = p.matcher(sql);
  301. final StringBuffer buffer = new StringBuffer();
  302. List<IndexedArgument> indexedArgs = Utils.newArrayList();
  303. int count = 0;
  304. while (m.find()) {
  305. String binding = m.group(1);
  306. m.appendReplacement(buffer, "?");
  307. IndexedArgument indexedArg;
  308. if ("?".equals(binding)) {
  309. // standard ? JDBC placeholder
  310. indexedArg = parameterIndex.get("arg" + count);
  311. } else {
  312. // named placeholder
  313. indexedArg = parameterIndex.get(binding);
  314. }
  315. if (indexedArg == null) {
  316. throw new IciqlException("Unbound SQL parameter '{0}' in {1}.{2}",
  317. binding, method.getDeclaringClass().getSimpleName(), method.getName());
  318. }
  319. indexedArgs.add(indexedArg);
  320. count++;
  321. }
  322. m.appendTail(buffer);
  323. final String statement = buffer.toString();
  324. // create an IndexedSql container for the statement and indexes
  325. return new IndexedSql(statement, Collections.unmodifiableList(indexedArgs));
  326. }
  327. /**
  328. * Builds an index of parameter name->(position,typeAdapter) from the method arguments
  329. * array. This index is calculated once per method.
  330. *
  331. * @param method
  332. * @return a bindings map of ("name", IndexedArgument) pairs
  333. */
  334. private Map<String, IndexedArgument> buildParameterIndex(Method method) {
  335. Map<String, IndexedArgument> index = new TreeMap<String, IndexedArgument>();
  336. Annotation [][] annotationsMatrix = method.getParameterAnnotations();
  337. for (int i = 0; i < annotationsMatrix.length; i++) {
  338. Annotation [] annotations = annotationsMatrix[i];
  339. /*
  340. * Conditionally map the bean properties of the method argument
  341. * class to Method and Field instances.
  342. */
  343. BindBean bean = getAnnotation(BindBean.class, annotations);
  344. if (bean != null) {
  345. final String prefix = bean.value();
  346. final Class<?> argumentClass = method.getParameterTypes()[i];
  347. Map<String, IndexedArgument> beanIndex = buildBeanIndex(i, prefix, argumentClass);
  348. index.putAll(beanIndex);
  349. }
  350. Class<? extends DataTypeAdapter<?>> typeAdapter = Utils.getDataTypeAdapter(annotations);
  351. final IndexedArgument indexedArgument = new IndexedArgument(i, typeAdapter);
  352. // :N - 1-indexed, like JDBC ResultSet
  353. index.put("" + (i + 1), indexedArgument);
  354. // argN - 0-indexed, like Reflection
  355. index.put("arg" + i, indexedArgument);
  356. // Bound name
  357. Bind binding = getAnnotation(Bind.class, annotations);
  358. if (binding!= null && !binding.value().isEmpty()) {
  359. index.put(binding.value(), indexedArgument);
  360. }
  361. // try mapping Java 8 argument names, may overwrite argN
  362. try {
  363. Class<?> nullArgs = null;
  364. Method getParameters = method.getClass().getMethod("getParameters", nullArgs);
  365. if (getParameters != null) {
  366. Object [] parameters = (Object []) getParameters.invoke(method, nullArgs);
  367. if (parameters != null) {
  368. Object o = parameters[i];
  369. Method getName = o.getClass().getMethod("getName", nullArgs);
  370. String j8name = getName.invoke(o, nullArgs).toString();
  371. if (!j8name.isEmpty()) {
  372. index.put(j8name, indexedArgument);
  373. }
  374. }
  375. }
  376. } catch (Throwable t) {
  377. }
  378. }
  379. return index;
  380. }
  381. /**
  382. * Builds an index of parameter name->(position,method) from the method arguments
  383. * array. This index is calculated once per method.
  384. *
  385. * @param argumentIndex
  386. * @param prefix
  387. * @param beanClass
  388. * @return a bindings map of ("prefix.property", IndexedArgument) pairs
  389. */
  390. private Map<String, IndexedArgument> buildBeanIndex(int argumentIndex, String prefix, Class<?> beanClass) {
  391. final String beanPrefix = StringUtils.isNullOrEmpty(prefix) ? "" : (prefix + ".");
  392. final Map<String, IndexedArgument> index = new TreeMap<String, IndexedArgument>();
  393. // map JavaBean property getters
  394. for (Method method : beanClass.getMethods()) {
  395. if (Modifier.isStatic(method.getModifiers())
  396. || method.getReturnType() == void.class
  397. || method.getParameterTypes().length > 0
  398. || method.getDeclaringClass() == Object.class) {
  399. // not a JavaBean property
  400. continue;
  401. }
  402. final String propertyName;
  403. final String name = method.getName();
  404. if (name.startsWith("get")) {
  405. propertyName = method.getName().substring(3);
  406. } else if (name.startsWith("is")) {
  407. propertyName = method.getName().substring(2);
  408. } else {
  409. propertyName = null;
  410. }
  411. if (propertyName == null) {
  412. // not a conventional JavaBean property
  413. continue;
  414. }
  415. final String binding = beanPrefix + preparePropertyName(propertyName);
  416. final IndexedArgument indexedArg = new IndexedArgument(argumentIndex, method);
  417. index.put(binding, indexedArg);
  418. }
  419. // map public instance fields
  420. for (Field field : beanClass.getFields()) {
  421. if (Modifier.isStatic(field.getModifiers())) {
  422. // not a JavaBean property
  423. continue;
  424. }
  425. final String binding = beanPrefix + preparePropertyName(field.getName());
  426. final IndexedArgument indexedArg = new IndexedArgument(argumentIndex, field);
  427. index.put(binding, indexedArg);
  428. }
  429. return index;
  430. }
  431. @SuppressWarnings("unchecked")
  432. private <T> T getAnnotation(Class<T> annotationClass, Annotation [] annotations) {
  433. if (annotations != null) {
  434. for (Annotation annotation : annotations) {
  435. if (annotation.annotationType() == annotationClass) {
  436. return (T) annotation;
  437. }
  438. }
  439. }
  440. return null;
  441. }
  442. private String preparePropertyName(String value) {
  443. return Character.toLowerCase(value.charAt(0)) + value.substring(1);
  444. }
  445. /*
  446. *
  447. * Standard Dao method implementations delegate to the underlying Db
  448. *
  449. */
  450. @Override
  451. public final Db db() {
  452. return db;
  453. }
  454. @Override
  455. public final <T> boolean insert(T t) {
  456. return db.insert(t);
  457. }
  458. @Override
  459. public final <T> void insertAll(List<T> t) {
  460. db.insertAll(t);
  461. }
  462. @Override
  463. public final <T> long insertAndGetKey(T t) {
  464. return db.insertAndGetKey(t);
  465. }
  466. @Override
  467. public final <T> List<Long> insertAllAndGetKeys(List<T> t) {
  468. return db.insertAllAndGetKeys(t);
  469. }
  470. @Override
  471. public final <T> boolean update(T t) {
  472. return db.update(t);
  473. }
  474. @Override
  475. public final <T> void updateAll(List<T> t) {
  476. db.updateAll(t);
  477. }
  478. @Override
  479. public final <T> void merge(T t) {
  480. db.merge(t);
  481. }
  482. @Override
  483. public final <T> boolean delete(T t) {
  484. return db.delete(t);
  485. }
  486. @Override
  487. public final <T> void deleteAll(List<T> t) {
  488. db.deleteAll(t);
  489. }
  490. @Override
  491. public final void close() {
  492. db.close();
  493. }
  494. /**
  495. * Container class to hold the prepared JDBC SQL statement and execution
  496. * parameters.
  497. */
  498. private class PreparedSql {
  499. final String sql;
  500. final Object [] parameters;
  501. PreparedSql(String sql, Object [] parameters) {
  502. this.sql = sql;
  503. this.parameters = parameters;
  504. }
  505. @Override
  506. public String toString() {
  507. return sql;
  508. }
  509. }
  510. /**
  511. * Container class to hold a parsed JDBC SQL statement and
  512. * IndexedParameters.
  513. * <p>
  514. * Instances of this class are cached because they are functional processing
  515. * containers as they contain Method and Field references for binding beans
  516. * and matching to method arguments.
  517. * </p>
  518. */
  519. private class IndexedSql {
  520. final String sql;
  521. final List<IndexedArgument> indexedArgs;
  522. IndexedSql(String sql, List<IndexedArgument> indexedArgs) {
  523. this.sql = sql;
  524. this.indexedArgs = indexedArgs;
  525. }
  526. /**
  527. * Prepares the method arguments for statement execution.
  528. *
  529. * @param db
  530. * @param methodArgs
  531. * @return the prepared sql statement and parameters
  532. */
  533. PreparedSql prepareSql(Db db, Object [] methodArgs) {
  534. Object [] parameters = new Object[indexedArgs.size()];
  535. for (int i = 0; i < indexedArgs.size(); i++) {
  536. IndexedArgument indexedArg = indexedArgs.get(i);
  537. Object methodArg = methodArgs[indexedArg.index];
  538. Object value = methodArg;
  539. Class<? extends DataTypeAdapter<?>> typeAdapter = indexedArg.typeAdapter;
  540. if (indexedArg.method != null) {
  541. // execute the bean method
  542. try {
  543. value = indexedArg.method.invoke(methodArg);
  544. typeAdapter = Utils.getDataTypeAdapter(indexedArg.method.getAnnotations());
  545. } catch (Exception e) {
  546. throw new IciqlException(e);
  547. }
  548. } else if (indexedArg.field != null) {
  549. // extract the field value
  550. try {
  551. value = indexedArg.field.get(methodArg);
  552. typeAdapter = Utils.getDataTypeAdapter(indexedArg.field.getAnnotations());
  553. } catch (Exception e) {
  554. throw new IciqlException(e);
  555. }
  556. } else if (typeAdapter == null) {
  557. // identify the type adapter for the argument class
  558. typeAdapter = Utils.getDataTypeAdapter(methodArg.getClass().getAnnotations());
  559. }
  560. // prepare the parameter
  561. parameters[i] = prepareParameter(db, value, typeAdapter);
  562. }
  563. return new PreparedSql(sql, parameters);
  564. }
  565. /**
  566. * Prepares a method argument to an sql parameter for transmission to a
  567. * database.
  568. *
  569. * @param db
  570. * @param arg
  571. * @param typeAdapter
  572. * @return a prepared parameter
  573. */
  574. private Object prepareParameter(Db db, Object arg, Class<? extends DataTypeAdapter<?>> typeAdapter) {
  575. if (typeAdapter != null) {
  576. // use a type adapter to serialize the method argument
  577. Object o = db.getDialect().serialize(arg, typeAdapter);
  578. return o;
  579. } else {
  580. // use the method argument
  581. return arg;
  582. }
  583. }
  584. @Override
  585. public String toString() {
  586. return sql;
  587. }
  588. }
  589. /**
  590. * IndexedArgument holds cached information about how to process an method
  591. * argument by it's index in the method arguments array.
  592. * <p>
  593. * An argument may be passed-through, might be bound to a bean property,
  594. * might be transformed with a type adapter, or a combination of these.
  595. * </p>
  596. */
  597. private class IndexedArgument {
  598. final int index;
  599. final Class<? extends DataTypeAdapter<?>> typeAdapter;
  600. final Method method;
  601. final Field field;
  602. IndexedArgument(int index, Class<? extends DataTypeAdapter<?>> typeAdapter) {
  603. this.index = index;
  604. this.typeAdapter = typeAdapter;
  605. this.method = null;
  606. this.field = null;
  607. }
  608. IndexedArgument(int methodArgIndex, Method method) {
  609. this.index = methodArgIndex;
  610. this.typeAdapter = null;
  611. this.method = method;
  612. this.field = null;
  613. }
  614. IndexedArgument(int methodArgIndex, Field field) {
  615. this.index = methodArgIndex;
  616. this.typeAdapter = null;
  617. this.method = null;
  618. this.field = field;
  619. }
  620. @Override
  621. public String toString() {
  622. String accessor;
  623. if (method != null) {
  624. accessor = "M:" + method.getDeclaringClass().getSimpleName() + "." + method.getName();
  625. } else if (field != null) {
  626. accessor = "F:" + field.getDeclaringClass().getSimpleName() + "." + field.getName();
  627. } else {
  628. accessor = "A:arg";
  629. }
  630. return index + ":" + accessor + (typeAdapter == null ? "" : (":" + typeAdapter.getSimpleName()));
  631. }
  632. }
  633. }