ParamNameResolver.java

  1. /*
  2.  *    Copyright 2009-2024 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.  *       https://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.   public static final String[] GENERIC_NAME_CACHE = new String[10];

  34.   static {
  35.     for (int i = 0; i < 10; i++) {
  36.       GENERIC_NAME_CACHE[i] = GENERIC_NAME_PREFIX + (i + 1);
  37.     }
  38.   }

  39.   private final boolean useActualParamName;

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

  52.   private boolean hasParamAnnotation;

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

  88.   private String getActualParamName(Method method, int paramIndex) {
  89.     return ParamNameUtil.getParamNames(method).get(paramIndex);
  90.   }

  91.   private static boolean isSpecialParameter(Class<?> clazz) {
  92.     return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
  93.   }

  94.   /**
  95.    * Returns parameter names referenced by SQL providers.
  96.    *
  97.    * @return the names
  98.    */
  99.   public String[] getNames() {
  100.     return names.values().toArray(new String[0]);
  101.   }

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

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

  165. }