View Javadoc
1   /**
2    *    Copyright 2009-2019 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  
18  import java.lang.annotation.Annotation;
19  import java.lang.reflect.Method;
20  import java.util.Collections;
21  import java.util.Map;
22  import java.util.SortedMap;
23  import java.util.TreeMap;
24  
25  import org.apache.ibatis.annotations.Param;
26  import org.apache.ibatis.binding.MapperMethod.ParamMap;
27  import org.apache.ibatis.session.Configuration;
28  import org.apache.ibatis.session.ResultHandler;
29  import org.apache.ibatis.session.RowBounds;
30  
31  public class ParamNameResolver {
32  
33    public static final String GENERIC_NAME_PREFIX = "param";
34  
35    /**
36     * <p>
37     * The key is the index and the value is the name of the parameter.<br />
38     * The name is obtained from {@link Param} if specified. When {@link Param} is not specified,
39     * the parameter index is used. Note that this index could be different from the actual index
40     * when the method has special parameters (i.e. {@link RowBounds} or {@link ResultHandler}).
41     * </p>
42     * <ul>
43     * <li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}}</li>
44     * <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li>
45     * <li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li>
46     * </ul>
47     */
48    private final SortedMap<Integer, String> names;
49  
50    private boolean hasParamAnnotation;
51  
52    public ParamNameResolver(Configuration config, Method method) {
53      final Class<?>[] paramTypes = method.getParameterTypes();
54      final Annotation[][] paramAnnotations = method.getParameterAnnotations();
55      final SortedMap<Integer, String> map = new TreeMap<>();
56      int paramCount = paramAnnotations.length;
57      // get names from @Param annotations
58      for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
59        if (isSpecialParameter(paramTypes[paramIndex])) {
60          // skip special parameters
61          continue;
62        }
63        String name = null;
64        for (Annotation annotation : paramAnnotations[paramIndex]) {
65          if (annotation instanceof Param) {
66            hasParamAnnotation = true;
67            name = ((Param) annotation).value();
68            break;
69          }
70        }
71        if (name == null) {
72          // @Param was not specified.
73          if (config.isUseActualParamName()) {
74            name = getActualParamName(method, paramIndex);
75          }
76          if (name == null) {
77            // use the parameter index as the name ("0", "1", ...)
78            // gcode issue #71
79            name = String.valueOf(map.size());
80          }
81        }
82        map.put(paramIndex, name);
83      }
84      names = Collections.unmodifiableSortedMap(map);
85    }
86  
87    private String getActualParamName(Method method, int paramIndex) {
88      return ParamNameUtil.getParamNames(method).get(paramIndex);
89    }
90  
91    private static boolean isSpecialParameter(Class<?> clazz) {
92      return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
93    }
94  
95    /**
96     * Returns parameter names referenced by SQL providers.
97     */
98    public String[] getNames() {
99      return names.values().toArray(new String[0]);
100   }
101 
102   /**
103    * <p>
104    * A single non-special parameter is returned without a name.
105    * Multiple parameters are named using the naming rule.
106    * In addition to the default names, this method also adds the generic names (param1, param2,
107    * ...).
108    * </p>
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       return args[names.firstKey()];
116     } else {
117       final Map<String, Object> param = new ParamMap<>();
118       int i = 0;
119       for (Map.Entry<Integer, String> entry : names.entrySet()) {
120         param.put(entry.getValue(), args[entry.getKey()]);
121         // add generic param names (param1, param2, ...)
122         final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
123         // ensure not to overwrite parameter named with @Param
124         if (!names.containsValue(genericParamName)) {
125           param.put(genericParamName, args[entry.getKey()]);
126         }
127         i++;
128       }
129       return param;
130     }
131   }
132 }