ParamNameResolver.java

  1. /*
  2.  *    Copyright 2009-2021 the original author or authors.
  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 org.apache.ibatis.reflection;

  17. import java.lang.annotation.Annotation;
  18. import java.lang.reflect.Method;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Optional;
  24. import java.util.SortedMap;
  25. import java.util.TreeMap;

  26. import org.apache.ibatis.annotations.Param;
  27. import org.apache.ibatis.binding.MapperMethod.ParamMap;
  28. import org.apache.ibatis.session.Configuration;
  29. import org.apache.ibatis.session.ResultHandler;
  30. import org.apache.ibatis.session.RowBounds;

  31. public class ParamNameResolver {

  32.   public static final String GENERIC_NAME_PREFIX = "param";

  33.   private final boolean useActualParamName;

  34.   /**
  35.    * <p>
  36.    * The key is the index and the value is the name of the parameter.<br />
  37.    * The name is obtained from {@link Param} if specified. When {@link Param} is not specified,
  38.    * the parameter index is used. Note that this index could be different from the actual index
  39.    * when the method has special parameters (i.e. {@link RowBounds} or {@link ResultHandler}).
  40.    * </p>
  41.    * <ul>
  42.    * <li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}}</li>
  43.    * <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li>
  44.    * <li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li>
  45.    * </ul>
  46.    */
  47.   private final SortedMap<Integer, String> names;

  48.   private boolean hasParamAnnotation;

  49.   public ParamNameResolver(Configuration config, Method method) {
  50.     this.useActualParamName = config.isUseActualParamName();
  51.     final Class<?>[] paramTypes = method.getParameterTypes();
  52.     final Annotation[][] paramAnnotations = method.getParameterAnnotations();
  53.     final SortedMap<Integer, String> map = new TreeMap<>();
  54.     int paramCount = paramAnnotations.length;
  55.     // get names from @Param annotations
  56.     for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
  57.       if (isSpecialParameter(paramTypes[paramIndex])) {
  58.         // skip special parameters
  59.         continue;
  60.       }
  61.       String name = null;
  62.       for (Annotation annotation : paramAnnotations[paramIndex]) {
  63.         if (annotation instanceof Param) {
  64.           hasParamAnnotation = true;
  65.           name = ((Param) annotation).value();
  66.           break;
  67.         }
  68.       }
  69.       if (name == null) {
  70.         // @Param was not specified.
  71.         if (useActualParamName) {
  72.           name = getActualParamName(method, paramIndex);
  73.         }
  74.         if (name == null) {
  75.           // use the parameter index as the name ("0", "1", ...)
  76.           // gcode issue #71
  77.           name = String.valueOf(map.size());
  78.         }
  79.       }
  80.       map.put(paramIndex, name);
  81.     }
  82.     names = Collections.unmodifiableSortedMap(map);
  83.   }

  84.   private String getActualParamName(Method method, int paramIndex) {
  85.     return ParamNameUtil.getParamNames(method).get(paramIndex);
  86.   }

  87.   private static boolean isSpecialParameter(Class<?> clazz) {
  88.     return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
  89.   }

  90.   /**
  91.    * Returns parameter names referenced by SQL providers.
  92.    *
  93.    * @return the names
  94.    */
  95.   public String[] getNames() {
  96.     return names.values().toArray(new String[0]);
  97.   }

  98.   /**
  99.    * <p>
  100.    * A single non-special parameter is returned without a name.
  101.    * Multiple parameters are named using the naming rule.
  102.    * In addition to the default names, this method also adds the generic names (param1, param2,
  103.    * ...).
  104.    * </p>
  105.    *
  106.    * @param args
  107.    *          the args
  108.    * @return the named params
  109.    */
  110.   public Object getNamedParams(Object[] args) {
  111.     final int paramCount = names.size();
  112.     if (args == null || paramCount == 0) {
  113.       return null;
  114.     } else if (!hasParamAnnotation && paramCount == 1) {
  115.       Object value = args[names.firstKey()];
  116.       return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
  117.     } else {
  118.       final Map<String, Object> param = new ParamMap<>();
  119.       int i = 0;
  120.       for (Map.Entry<Integer, String> entry : names.entrySet()) {
  121.         param.put(entry.getValue(), args[entry.getKey()]);
  122.         // add generic param names (param1, param2, ...)
  123.         final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
  124.         // ensure not to overwrite parameter named with @Param
  125.         if (!names.containsValue(genericParamName)) {
  126.           param.put(genericParamName, args[entry.getKey()]);
  127.         }
  128.         i++;
  129.       }
  130.       return param;
  131.     }
  132.   }

  133.   /**
  134.    * Wrap to a {@link ParamMap} if object is {@link Collection} or array.
  135.    *
  136.    * @param object a parameter object
  137.    * @param actualParamName an actual parameter name
  138.    *                        (If specify a name, set an object to {@link ParamMap} with specified name)
  139.    * @return a {@link ParamMap}
  140.    * @since 3.5.5
  141.    */
  142.   public static Object wrapToMapIfCollection(Object object, String actualParamName) {
  143.     if (object instanceof Collection) {
  144.       ParamMap<Object> map = new ParamMap<>();
  145.       map.put("collection", object);
  146.       if (object instanceof List) {
  147.         map.put("list", object);
  148.       }
  149.       Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
  150.       return map;
  151.     } else if (object != null && object.getClass().isArray()) {
  152.       ParamMap<Object> map = new ParamMap<>();
  153.       map.put("array", object);
  154.       Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
  155.       return map;
  156.     }
  157.     return object;
  158.   }

  159. }